web-dev-qa-db-fra.com

Impossible d'utiliser la classe enum comme clé unordered_map

J'ai une classe contenant une classe enum.

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

Ensuite, quand j'implémente le code suivant dans une autre classe ...

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

... Je reçois une erreur de compilation.

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

Qu'est-ce qui cause l'erreur ici?

70
Appleshell

J'utilise un objet foncteur pour calculer le hash de enum class:

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

Vous pouvez maintenant l'utiliser comme 3ème template-paramètre de std::unordered_map:

enum class MyEnum {};

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

Donc, vous n'avez pas besoin de fournir une spécialisation de std::hash, la déduction d’argument de gabarit fait le travail. De plus, vous pouvez utiliser le Word using et créer votre propre unordered_map qui utilise std::hash ou EnumClassHash selon le type Key:

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>>;

Maintenant, vous pouvez utiliser MyUnorderedMap avec enum class ou un autre type:

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

Théoriquement, HashType pourrait utiliser std::underlying_type puis le EnumClassHash ne sera pas nécessaire. Cela pourrait être quelque chose comme ça, mais je n'ai pas encore essayé:

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

Si vous utilisez std::underlying_type fonctionne, pourrait être une très bonne proposition pour la norme.

107
Daniel

Ceci était considéré comme un défaut de la norme et a été corrigé en C++ 14: http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#2148

À partir de la version 4.9.3 de gcc, cette résolution n’est pas encore implémentée dans libstdc ++: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=6097 .

Il a été corrigé dans la libc ++ de clang en 2013: http://lists.cs.uiuc.edu/pipermail/cfe-commits/Week-of-Mon-20130902/087778.html

40
David Stone

Une solution très simple serait de fournir un objet de fonction de hachage comme ceci:

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

C'est tout pour une clé enum, pas besoin de fournir une spécialisation de std :: hash.

20
denim

Comme KerrekSB l'a souligné, vous devez fournir une spécialisation de std::hash si vous voulez utiliser std::unordered_map, quelque chose comme:

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

Quand vous utilisez std::unordered_map, vous savez que vous avez besoin d’une fonction de hachage. Pour les types intégrés ou STL, des valeurs par défaut sont disponibles, mais pas pour celles définies par l'utilisateur. Si vous avez juste besoin d’une carte, pourquoi ne pas essayer std::map?

5
CS Pei

Ajoutez ceci à l'entête définissant MyEnumClass:

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