关于C ++:不能将枚举类用作unordered_map键

Can't use enum class as unordered_map key

我有一个包含枚举类的类。

1
2
3
4
5
6
7
8
class Shader {
public:
    enum class Type {
        Vertex   = GL_VERTEX_SHADER,
        Geometry = GL_GEOMETRY_SHADER,
        Fragment = GL_FRAGMENT_SHADER
    };
    //...

然后,当我在另一个类中实现以下代码时...

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

...我得到一个编译错误。

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

这是什么导致错误?


我使用函子对象来计算enum class的哈希值:

1
2
3
4
5
6
7
8
struct EnumClassHash
{
    template <typename T>
    std::size_t operator()(T t) const
    {
        return static_cast<std::size_t>(t);
    }
};

现在,您可以将其用作std::unordered_map的第三个模板参数:

1
2
3
enum class MyEnum {};

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

因此,您无需提供std::hash的特殊化,模板参数推导即可完成。此外,您可以使用单词using并根据Key类型使用std::hashEnumClassHash来制作自己的unordered_map

1
2
3
4
5
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>>;

现在,您可以将MyUnorderedMapenum class或其他类型一起使用:

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

从理论上讲,HashType可以使用std::underlying_type,然后EnumClassHash就不是必需的。可能是这样的,但是我还没有尝试过:

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

如果使用std::underlying_type可行,则可能是该标准的很好建议。


这被认为是标准中的缺陷,并且已在C ++ 14中修复:http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#2148

自6.1起,gcc随附的libstdc ++版本中已修复此问题:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60970。

它在2013年用clang的libc ++修复:http://lists.cs.uiuc.edu/pipermail/cfe-commits/Week-of-Mon-20130902/087778.html


一个非常简单的解决方案是提供一个哈希函数对象,如下所示:

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

这就是枚举键的全部内容,无需提供std :: hash的特殊化。


正如KerrekSB所指出的,如果要使用std::unordered_map,则需要提供std::hash的特殊化,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 ) );
        }
    };
}

使用std::unordered_map时,您知道需要一个哈希函数。对于内置或STL类型,有默认值可用,但对于用户定义的类型则没有。如果只需要地图,为什么不尝试std::map


将其添加到定义MyEnumClass的标头中:

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


尝试

1
std::unordered_map<Shader::Type, Shader, std::hash<std::underlying_type<Shader::Type>::type>> shaders;