不能将枚举类用作unordered_map键


77

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

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

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

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

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

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

这是什么导致错误?


6
您没有专门std::hash研究枚举类型。
Kerrek SB 2013年

Answers:


116

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

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

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

enum class MyEnum {};

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

因此,您无需提供的特殊化std::hash,模板参数推导即可完成工作。此外,你可以用这个词using,让你自己unordered_map在使用std::hashEnumClassHash依赖于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>>;

现在你可以使用MyUnorderedMap带有enum class或另一种类型:

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

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

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作品,对于该标准可能是一个很好的建议。


1
也许最简单,但是首先让枚举键起作用必须更简单吗?:-S
乔尼

从理论上讲,不行underlying_type。您展示了自己:必须有一个hash()带有MyEnumClass参数的函数。因此,当然,hashing会underlying_type调用一个期望一个int(或: yourEnumClassType)的函数。只是尝试underlying_type就会发现它给出了完全相同的错误:无法转换MyEnumClassint。如果只是通过underlying_type 成功了那么MyEnumClass直接通过就可以了。无论如何,正如David S所示,该问题现在已由工作组解决。如果只有GCC会发布他们的补丁…
underscore_d

如果枚举类是另一个类的受保护“成员”,这对我不起作用。我需要将枚举定义直接从名称空间定义内的类中移出。
Martin Pecka

1
由于某些原因,使用枚举类作为无序映射中的键完全没有问题。我使用clang,也许支持取决于编译器?编辑:作为另一个答案指出,这是标准的c ++ 14
johnbakers

1
应该更改接受的答案以指向答案,该答案首先指出该行为被视为标准中的缺陷,并且已在现代编译器中得到修复。
Michael Allwright

54

这被认为是标准中的缺陷,并已在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
我很惊讶,enum class作为关键在std::unordered_set编译的Visual Studio 2013年
理查德·戴利

3
@ypnos:这很可能是故意修复的。它认为这在Visual Studio 2012中不起作用,因此他们可能在2013年将其修复为该缺陷的一部分。实际上,Microsoft的C ++标准库维护者STL是提供解决该缺陷的措辞的人。
大卫·斯通

1
@ypnos:我更喜欢Visual Studio的方法,即让所有人使用该语言的最新版本,因为C ++现在C ++ 14。
David Stone

2
@DavidStone:事实上,当前的Visual Studio不支持C ++ 14甚至C ++ 11。它们都包含C99,Visual Studio不支持。我并不是说VS不应该默认为最新的语言版本。我是说,当所有竞争的编译器都支持某些可用的标准时,它不应该引入自己的实际标准。VS 2013在C ++ 14最终确定之前整整发布了一年。但是,它没有完全支持C ++ 11,而是包括C ++ 14功能的子集。
ypnos

2
相信我,我不仅向您扔了一个链接-首先,页面确实描述了C ++ 14,只是向上滚动。二,“垃圾收集最小的支持”符合标准的。如果您阅读规范(链接到那里!):“不支持垃圾回收并实现此处描述为no-ops的所有库调用的实现是符合标准的。” 这就是GCC所做的。而且,我看不出您为哪个平台编写代码或您觉得舒适的编译器如何在讨论中增加任何东西,这与标准一致性有关,而与个人认为的编译器质量无关。
ypnos

22

一个非常简单的解决方案是提供一个像这样的哈希函数对象:

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

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


27
这适用于旧式枚举,但不适用于OP使用的新的“枚举类”。
BrandonLWhite 2014年

5
当它公然不回答问题时,如何达到+8?
underscore_d

^好吧,按照下面弗拉基米尔的回答,也许牛仔布在VS2012或某种程度上允许这样做的其他编译器中对此进行了测试。
underscore_d

6

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

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

1
您不应该添加const noexcept签名吗?
einpoklum

std不幸的是,扩展是未定义的行为。
维克托·波勒沃

3
@VictorPolevoy:扩展std:对于这种特殊情况,幸运的是定义了行为。cn.cppreference.com/w/cpp/language/extending_std
galinette

我认为llvm 8.1对此有问题,但在其他编译器上工作正常。
Moshe Rabaev

5

正如KerrekSB指出的那样,您需要提供有关std::hash是否要使用的专业信息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 ) );
        }
    };
}

5

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


29
std::unordered_map在几乎所有情况下都具有出色的性能,因此应该将其视为默认值而不是std::map
David Stone

-1

尝试

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

不行 那std::hash()会期望underlying_type作为参数的实例,但是会得到MyEnumClass。当您尝试使用enum指定的旧普通解决方案时,这完全相同std::hash<int>。难道提示之前尝试这个?
underscore_d

当然,我做到了。可以在VS 2012中很好地进行编译。正是这个“命名空间ObjectDefines {枚举ObjectType {ObjectHigh,....}}} std :: unordered_map <ObjectDefines :: ObjectType,ObjectData *,std :: hash <std :: underlying_type <ObjectDefines :: ObjectType > :: type >> m_mapEntry;“
弗拉基米尔·舒图(Fladimir Shutow)'16

问题是关于enum class,而不是C样式的unscoped enum。您会看到我的评论enum class对主题是正确的。
underscore_d

我看到了区别。但是它仍然可以正常编译:class ObjectDefines {public:枚举class ObjectType {ObjectHigh,ObjectLow}; }; std :: unordered_map <ObjectDefines :: ObjectType,ObjectDefines *,std :: hash <std :: underlying_type <ObjectDefines :: ObjectType> :: type >> m_mapEntry;
弗拉基米尔·舒托

1
在GCC 4.7.3它编译太(这里检查melpon.org/wandbox/permlink/k2FopvmxQeQczKtE
弗拉基米尔Shutow
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.