90 votes

Impossible d'utiliser une classe d'énumération comme clé d'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, lorsque j'implémente le code suivant dans une autre classe...

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

...j'obtiens une erreur de compilation.

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

Quelle est la cause de l'erreur ici ?

126voto

Daniel Points 1531

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

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

Maintenant, vous pouvez l'utiliser comme troisième paramètre de modèle de std::unordered_map :

enum class MyEnum {};

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

Ainsi, vous n'avez pas besoin de fournir une spécialisation de std::hash la déduction de l'argument du modèle fait le travail. En outre, vous pouvez utiliser le mot using et faites votre propre unordered_map qui utilisent std::hash o EnumClassHash en fonction de la Key type :

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

Vous pouvez maintenant utiliser MyUnorderedMap con enum class ou un autre type :

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

Théoriquement, HashType pourrait utiliser std::underlying_type et ensuite 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.

60voto

David Stone Points 3822

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

Ce problème est corrigé dans la version de libstdc++ fournie avec gcc à partir de la version 6.1 : https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60970 .

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

26voto

denim Points 379

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.

7voto

user3080602 Points 46

Ajoutez ceci à l'en-tête définissant MyEnumClass :

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

5voto

Daniel Frey Points 30752

Comme l'a souligné KerrekSB, vous devez fournir une spécialisation de l'option 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 ) );
        }
    };
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X