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

76

Ich habe eine Klasse, die eine Aufzählungsklasse enthält.

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 erhalte einen Kompilierungsfehler.

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

Was verursacht hier den Fehler?

Apfelschale
quelle
6
Sie haben sich nicht auf std::hashden Aufzählungstyp spezialisiert .
Kerrek SB

Antworten:

115

Ich benutze ein Funktorobjekt, um den Hash von zu berechnen enum class:

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 verwenden std::unordered_map:

enum class MyEnum {};

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

Sie müssen also keine Spezialisierung std::hashangeben. Der Abzug der Vorlagenargumente erledigt den Job. Darüber hinaus können Sie das Wort verwenden usingund Ihr eigenes erstellen unordered_map, das std::hashoder EnumClassHashje nach KeyTyp Folgendes 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 MyUnorderedMapmit enum classoder einem anderen Typ verwenden:

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

Theoretisch HashTypekönnte man das gebrauchen std::underlying_typeund dann EnumClassHashwird das nicht nötig sein. 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 std::underlying_typeWerke verwendet werden, könnte dies ein sehr guter Vorschlag für den Standard sein.

Daniel
quelle
1
Am einfachsten vielleicht, aber es muss einfacher sein, Enum-Schlüssel überhaupt erst zum Laufen zu bringen? : -S
Jonny
"Theoretisch", nein, underlying_typewird nicht funktionieren. Sie haben sich gezeigt: Es muss eine hash()Funktion geben, die einen MyEnumClassParameter akzeptiert. Also, natürlich, hashing auf underlying_typeruft eine Funktion , die eine erwartet int(oder : yourEnumClassType). Ich versuche nur, underlying_typehabe gezeigt , dass es genau die gleichen Fehler gibt: nicht umwandeln kann MyEnumClasszu int. Wenn nur beiläufig underlying_type tut Arbeit, so würde vorbei MyEnumClassdirekt in dem ersten Platz. Wie David S zeigt, wurde dies nun von der Arbeitsgruppe behoben. Wenn nur GCC jemals ihren Patch veröffentlichen würde ...
underscore_d
Dies funktionierte bei mir nicht, falls die Enum-Klasse ein geschütztes "Mitglied" einer anderen Klasse war. Ich musste die Enum-Definition aus der Klasse direkt in eine Namespace-Definition verschieben.
Martin Pecka
1
Aus bestimmten Gründen habe ich überhaupt keine Probleme, eine Enum-Klasse als Schlüssel in einer ungeordneten Karte zu verwenden. Ich benutze Clang, vielleicht hängt die Unterstützung vom Compiler ab? edit: wie eine andere antwort darauf hinweist, ist dies im standard ab c ++ 14
johnbakers
1
Die akzeptierte Antwort sollte geändert werden, um auf die Antwort zu verweisen, die mit dem Hinweis beginnt, dass dieses Verhalten als Fehler im Standard angesehen wurde und in modernen Compilern behoben wurde.
Allsey87
54

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

Dies ist in der Version von libstdc ++ behoben, die mit gcc ab 6.1 ausgeliefert wird: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60970 .

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

David Stone
quelle
1
Ich bin überrascht enum class, als Schlüssel für std::unordered_setKompilierungen für Visual Studio 2013 verwendet zu werden.
Richard Dally
3
@ypnos: Es war höchstwahrscheinlich eine absichtliche Lösung. Es wird davon ausgegangen, dass dies in Visual Studio 2012 nicht funktioniert. Daher wurde es wahrscheinlich 2013 als Teil dieses Fehlers behoben. Tatsächlich ist STL, der C ++ - Standardbibliotheksbetreuer bei Microsoft, derjenige, der den Wortlaut zur Behebung des Fehlers bereitgestellt hat.
David Stone
1
@ypnos: Ich ziehe den Visual Studio Ansatz von nur darum , alle auf der neuesten Version der Sprache, wie C ++ ist C ++ 14 jetzt.
David Stone
2
@DavidStone: Tatsächlich unterstützt das aktuelle Visual Studio weder C ++ 14 noch C ++ 11. Beide enthalten C99, das von Visual Studio nicht unterstützt wird. Ich sage nicht, dass VS nicht standardmäßig die neueste Sprachversion verwenden sollte. Ich sage, es sollte keinen eigenen De-facto-Standard einführen, wenn bestimmte Standards verfügbar sind, die von allen konkurrierenden Compilern unterstützt werden. VS 2013 erschien ein volles Jahr vor der Fertigstellung von C ++ 14. Anstatt C ++ 11 vollständig zu unterstützen, enthält es eine Teilmenge der C ++ 14-Funktionen.
Ypnos
2
Glauben Sie mir, dass ich nicht nur einen Link auf du- Zuerst werfen, die Seite tut C beschreiben ++ 14 Scrollen Sie einfach auf. Zweitens, die „Minimal Unterstützung für die Garbage Collection“ ist standardkonforme. Wenn Sie die Spezifikation lesen (dort verlinkt!): "Eine Implementierung, die die Garbage Collection nicht unterstützt und alle hier als no-ops beschriebenen Bibliotheksaufrufe implementiert, ist konform." Dies ist, was GCC tut. Und ich sehe nicht, wie die Plattformen, für die Sie Code schreiben oder für welchen Compiler Sie sich wohl fühlen, der Diskussion etwas hinzufügen, bei dem es um die Konformität mit Standards und nicht um die individuell wahrgenommene Compilerqualität ging.
Ypnos
22

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 Enum-Schlüssel, ohne dass eine Spezialisierung auf std :: hash erforderlich ist.

Denim
quelle
27
Dies funktioniert für alte Aufzählungen, jedoch nicht für die neuen Hotness-Aufzählungsklassen, die das OP verwendet.
BrandonLWhite
5
Wie kam es zu +8, wenn es die Frage offensichtlich nicht beantwortet?
underscore_d
^ Nun, gemäß der Antwort von Vladimir unten, hat Denim dies vielleicht in VS2012 oder einem anderen Compiler getestet, der dies irgendwie zulässt.
underscore_d
6

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

namespace std {
  template <> struct hash<MyEnumClass> {
    size_t operator() (const MyEnumClass &t) const { return size_t(t); }
  };
}
user3080602
quelle
1
Sollten Sie nicht const noexceptzur Signatur hinzufügen ?
Einpoklum
Das Erweitern stdist leider undefiniertes Verhalten.
Victor Polevoy
3
@ VictorPolevoy: Erweiterung std: ist glücklicherweise definiertes Verhalten für diesen speziellen Fall. en.cppreference.com/w/cpp/language/extending_std
Galinette
Ich denke, llvm 8.1 hat ein Problem damit, aber auf anderen Compilern funktioniert gut.
Moshe Rabaev
5

Wie KerrekSB hervorhob, müssen Sie eine Spezialisierung angeben, std::hashwenn Sie Folgendes verwenden möchten std::unordered_map:

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 ) );
        }
    };
}
Daniel Frey
quelle
5

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

CS Pei
quelle
28
std::unordered_maphat in fast allen Situationen eine überlegene Leistung und sollte wahrscheinlich eher als Standard betrachtet werden als std::map.
David Stone
-1

Versuchen

std::unordered_map<Shader::Type, Shader, std::hash<std::underlying_type<Shader::Type>::type>> shaders;
Vladimir Shutow
quelle
Wird nicht funktionieren. Das std::hash()erwartet eine Instanz underlying_typeals Parameter, wird aber MyEnumClassstattdessen erhalten. Dies ist genau das Gleiche, wenn Sie versuchen, die alte einfache enumSpezifikationslösung zu verwenden std::hash<int>. Haben Sie dies versucht, bevor Sie es vorgeschlagen haben?
underscore_d
sicher habe ich. Kompiliert problemlos in VS 2012. Genau dieser "Namespace ObjectDefines {enum ObjectType {ObjectHigh, ....}} std :: unordered_map <ObjectDefines :: ObjectType, ObjectData *, std :: hash <std :: zugrunde liegender_Typ <ObjectDefines :: ObjectType > :: type >> m_mapEntry; "
Vladimir Shutow
Die Frage enum classbetrifft nicht C-artige enums. Sie werden sehen, dass mein Kommentar wahr ist enum class, was das Thema ist.
underscore_d
Ich sehe den Unterschied. Aber es wird immer noch gut kompiliert: class ObjectDefines {public: enum class ObjectType {ObjectHigh, ObjectLow}; }; std :: unordered_map <ObjectDefines :: ObjectType, ObjectDefines *, std :: hash <std :: zugrunde liegender_Typ <ObjectDefines :: ObjectType> :: type >> m_mapEntry;
Vladimir Shutow
1
in gcc 4.7.3 wird es auch kompiliert (hier überprüft melpon.org/wandbox/permlink/k2FopvmxQeQczKtE )
Vladimir Shutow