wake-up-neo.net

Die Enum-Klasse kann nicht als unordered_map-Schlüssel verwendet werden

Ich habe eine Klasse mit einer Aufzählungsklasse.

class Shader {
public:
    enum class Type {
        Vertex   = GL_VERTEX_SHADER,
        Geometry = GL_GEOMETRY_SHADER,
        Fragment = GL_FRAGMENT_SHADER
    };
    //...

Wenn ich dann den folgenden Code in einer anderen Klasse implementiere ...

std::unordered_map<Shader::Type, Shader> shaders;

... Ich bekomme einen Kompilierungsfehler.

...usr/lib/c++/v1/type_traits:770:38: 
Implicit instantiation of undefined template 'std::__1::hash<Shader::Type>'

Was ist hier die Fehlerursache?

70
Appleshell

Ich benutze ein functor Objekt um einen Hash von enum class Zu berechnen:

struct EnumClassHash
{
    template <typename T>
    std::size_t operator()(T t) const
    {
        return static_cast<std::size_t>(t);
    }
};

Jetzt können Sie es als 3. Template-Parameter von std::unordered_map Verwenden:

enum class MyEnum {};

std::unordered_map<MyEnum, int, EnumClassHash> myMap;

Damit Sie keine Spezialisierung von std::hash Angeben müssen, erledigt der Template-Argument-Abzug den Job. Außerdem können Sie das Wort using verwenden und Ihren eigenen unordered_map Erstellen, der je nach Typ EnumClassHashstd::hash Oder Key verwendet:

template <typename Key>
using HashType = typename std::conditional<std::is_enum<Key>::value, EnumClassHash, std::hash<Key>>::type;

template <typename Key, typename T>
using MyUnorderedMap = std::unordered_map<Key, T, HashType<Key>>;

Jetzt können Sie MyUnorderedMap mit enum class Oder einem anderen Typ verwenden:

MyUnorderedMap<int, int> myMap2;
MyUnorderedMap<MyEnum, int> myMap3;

Theoretisch könnte HashTypestd::underlying_type Verwenden, und dann ist EnumClassHash nicht erforderlich. Das könnte so etwas sein, aber ich habe es noch nicht versucht:

template <typename Key>
using HashType = typename std::conditional<std::is_enum<Key>::value, std::hash<std::underlying_type<Key>::type>, std::hash<Key>>::type;

Wenn die Verwendung von std::underlying_type Funktioniert, könnte dies ein sehr guter Vorschlag für den Standard sein.

107
Daniel

Dies wurde als Standardfehler angesehen und in C++ 14 behoben: http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#2148

Ab gcc 4.9.3 ist diese Lösung jedoch in libstdc ++ noch nicht implementiert: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=6097 .

Es wurde 2013 in clangs libc ++ behoben: http://lists.cs.uiuc.edu/pipermail/cfe-commits/Week-of-Mon-20130902/087778.html

40
David Stone

Eine sehr einfache Lösung wäre, ein Hash-Funktionsobjekt wie das folgende bereitzustellen:

std::unordered_map<Shader::Type, Shader, std::hash<int> > shaders;

Das ist alles für einen Aufzählungsschlüssel, es ist nicht erforderlich, eine Spezialisierung von std :: hash anzugeben.

20
denim

Wie KerrekSB hervorhob, müssen Sie eine Spezialisierung von std::hash Angeben, wenn Sie std::unordered_map Verwenden möchten.

namespace std
{
    template<>
    struct hash< ::Shader::Type >
    {
        typedef ::Shader::Type argument_type;
        typedef std::underlying_type< argument_type >::type underlying_type;
        typedef std::hash< underlying_type >::result_type result_type;
        result_type operator()( const argument_type& arg ) const
        {
            std::hash< underlying_type > hasher;
            return hasher( static_cast< underlying_type >( arg ) );
        }
    };
}
5
Daniel Frey

Wenn Sie std::unordered_map, Sie wissen, dass Sie eine Hash-Funktion benötigen. Für integrierte oder STL Typen sind Standardeinstellungen verfügbar, nicht jedoch für benutzerdefinierte. Wenn du nur eine Karte brauchst, warum versuchst du es nicht mit std::map?

5
CS Pei

Fügen Sie dies zum Header hinzu, der MyEnumClass definiert:

namespace std {
  template <> struct hash<MyEnumClass> {
    size_t operator() (const MyEnumClass &t) const { return size_t(t); }
  };
}
4
user3080602