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::hash或EnumClassHash来制作自己的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>>; |
现在,您可以将MyUnorderedMap与enum 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可行,则可能是该标准的很好建议。
-
也许最简单,但是首先使枚举键起作用必须更简单吗? :-S
-
"理论上",不,underlying_type将不起作用。您展示了自己:必须有一个带有MyEnumClass参数的hash()函数。因此,当然,对underlying_type的hash调用会调用一个期望int(或: yourEnumClassType)的函数。仅尝试underlying_type就会表明它给出了完全相同的错误:无法将MyEnumClass转换为int。如果仅通过underlying_type确实有效,那么首先直接通过MyEnumClass也是可以的。无论如何,正如David S所示,该问题现在已由工作组解决。如果只有GCC会发布他们的补丁...
-
万一枚举类是另一个类的受保护"成员",这对我没有用。我需要将枚举定义直接从类中移出名称空间定义。
-
由于某些原因,我使用枚举类作为无序映射中的键完全没有问题。我使用clang,也许支持取决于编译器?编辑:作为另一个答案指出,这是从c ++ 14开始的标准
-
应该更改接受的答案以指向答案,该答案首先指出该行为被视为标准中的缺陷,并且已在现代编译器中得到修复。
这被认为是标准中的缺陷,并且已在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
-
我很惊讶enum class在Visual Studio 2013的std::unordered_set编译中用作键。
-
我不惊讶。各种各样的非标准狗屎都可以在Visual Studio中编译,自古以来就是这样。
-
@ypnos:这很可能是故意修复的。它认为这在Visual Studio 2012中不起作用,因此他们可能在2013年修复了该缺陷。实际上,Microsoft提供的C ++标准库维护者STL是提供解决该缺陷的措辞的人。
-
我不是说他们修复它或编译器执行随机操作是不好的。我只是说Microsoft总是提供一种标准遵从性的过时状态,其中支持最新标准的某些部分,不支持其他部分,并且较早地支持即将发布的标准的某些部分。这使得编写与其他工具链一起编译的标准兼容代码变得更加困难。在GCC或clang中,您可以指定要针对哪个标准版本进行编程。但是Windows程序员不会说"我编写C ++-11"或"我编写C89"。他们说"我编程VS 2012"…
-
@ypnos:我更喜欢Visual Studio的方法,即让所有人使用该语言的最新版本,因为C ++现在是C ++ 14。
-
@DavidStone:事实上,当前的Visual Studio不支持C ++ 14甚至C ++ 11。它们都包含C99,Visual Studio不支持。我不是说VS不应该默认为最新的语言版本。我是说,当所有竞争的编译器都支持某些可用的标准时,它不应引入自己的实际标准。 VS 2013在C ++ 14最终确定之前整整发布了一年。但是,它没有完全支持C ++ 11,而是包括C ++ 14功能的子集。
-
下面的@DavidStone Vladimirs回答表明VS2012确实在unordered_map中支持此(或某些变体),但是这样做可能是对还是错。
-
@ypnos是的,好吧,如上所示,gcc也不执行完整的标准,所以它的锅黑了。
-
@quant_dev你在说什么? GCC完全符合所有C ++标准,请参阅gcc.gnu.org/projects/cxx-status.html#cxx11。虽然GCC开发人员也需要时间来实施新标准,但他们仍在争取100%的标准确认,并拥有实现该标准的记录。这与Microsoft相反,后者故意不追求标准一致性,而是如他们所说,"满足客户要求的功能"。另外,我的主要观点是:在GCC,Clang等中。我可以选择要针对的标准版本,并在所有足够近期的编译器版本中获得预期的行为。
-
@ypnos您的页面根本没有描述C ++ 14,并且缺少1个C ++ 11功能("对垃圾收集和基于可达性的泄漏检测的最低支持为N2670,否")。
-
实际上,作为一个为Linux和Windows编写代码的C ++开发人员,我发现Visual Studio C ++是一个非常好用的编译器。在某些方面,它比海湾合作委员会更糟,在某些方面则更好。
-
相信我,我并没有向您抛出任何链接-首先,页面确实描述了C ++ 14,只是向上滚动。第二,"对垃圾收集的最小支持"符合标准。如果您阅读规范(链接到那里!):"不支持垃圾回收并实现此处描述为no-ops的所有库调用的实现是符合标准的。"这就是GCC所做的。而且我看不出您为哪个平台编写代码或您觉得合适的编译器如何增加讨论的内容,这是有关标准的一致性,而不是关于个人认为的编译器质量。
-
在gcc版本9.1.0(默认情况下启用了C ++ 11和C ++ 14功能)中,代码编译时没有任何警告或错误。
-
@ allsey87:更新了答案以反映gcc在6.1中已解决此问题
一个非常简单的解决方案是提供一个哈希函数对象,如下所示:
1
| std::unordered_map<Shader::Type, Shader, std::hash<int> > shaders; |
这就是枚举键的全部内容,无需提供std :: hash的特殊化。
-
这适用于旧式枚举,但不适用于OP使用的新的"枚举类"。
-
当它公然不回答问题时,如何达到+8?
-
^好吧,按照以下Vladimirs的回答,也许牛仔布在VS2012或某种允许该功能的其他编译器中对此进行了测试。
正如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?
-
std::unordered_map在几乎所有情况下均具有出色的性能,因此可能应将其视为比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); }
};
} |
-
您不应该在签名中添加const noexcept吗?
-
不幸的是,扩展std是未定义的行为。
-
@VictorPolevoy:扩展std:对于这种特殊情况,幸运的是定义了行为。 zh.cppreference.com/w/cpp/language/extending_std
-
@galinette哦,谢谢你的评论!
-
我认为llvm 8.1对此有问题,但在其他编译器上工作正常。
尝试
1
| std::unordered_map<Shader::Type, Shader, std::hash<std::underlying_type<Shader::Type>::type>> shaders; |
-
不工作。该std::hash()将期望underlying_type的实例作为参数,但将获得MyEnumClass。当您尝试使用指定std::hash的旧普通enum解决方案时,这完全相同。在建议之前您尝试过吗?
-
当然,我做到了。可以在VS 2012中很好地进行编译。正是这个"命名空间ObjectDefines {枚举ObjectType {ObjectHigh,....}}} std :: unordered_map :: type >> m_mapEntry;"
-
问题是关于enum class,而不是C风格的无作用域enum。您会看到我的评论对于主题enum class是正确的。
-
我看到了区别。但是它仍然可以正常编译:class ObjectDefines {public:枚举class ObjectType {ObjectHigh,ObjectLow}; }; std :: unordered_map :: type >> m_mapEntry;
-
有趣!根据其他答案,尤其是引用标准缺陷的答案,我非常确定这不应该起作用...(&我不知道他们是如何实现的。)像我之前的测试一样,将相同的代码添加到GCC中(添加了#include )产生一堆经典的模板化错误消息-第一个也是最有用的是usrincludec++5bitshashtable_policy.h:85:34: error: no match for call to ‘(const std::hash) (const ObjectDefines::ObjectType&)’ 。 LLVM / clang++同意。任何人都可以确认VS2012是对还是错,并且可以测试最新版本?
-
我在VS 2013中检查它也可以正常工作。
-
在gcc 4.7.3中它也可以编译(在此处检查melpon.org/wandbox/permlink/k2FopvmxQeQczKtE)
-
是的,但同一页面上的5.2.0中没有,或者最新的v4版本4.9.2中没有。这只会变得奇怪...