【问题标题】:Can't use enum class as unordered_map key不能使用枚举类作为 unordered_map 键
【发布时间】:2013-09-21 04:24:39
【问题描述】:

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

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

是什么导致了这里的错误?

【问题讨论】:

  • 你没有专门针对枚举类型 std::hash

标签: c++ c++11 map enums enum-class


【解决方案1】:

我使用函子对象计算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>>;

现在您可以将MyUnorderedMapenum 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 有效,那么对于标准来说可能是一个很好的提议。

【讨论】:

  • 可能是最简单的,但首先让枚举键工作一定更简单吗? :-S
  • “理论上”,不,underlying_type 不起作用。你展示了自己:必须有一个hash() 函数接受MyEnumClass 参数。所以,当然,hashing on underlying_type 会调用一个需要 int(或 : yourEnumClassType)的函数。只是尝试underlying_type 会显示它给出完全相同的错误:无法将MyEnumClass 转换为int。如果只是传递underlying_type did 工作,so 将首先直接传递MyEnumClass。无论如何,正如 David S 所示,WG 现在已经解决了这个问题。要是 GCC 能发布他们的补丁就好了……
  • 如果枚举类是另一个类的受保护“成员”,这对我不起作用。我需要将枚举定义直接移出命名空间定义中的类。
  • 由于某些原因,我在使用枚举类作为无序映射中的键时完全没有问题。我使用clang,也许支持取决于编译器?编辑:正如另一个答案指出的那样,这是 c++14 的标准
  • 应将接受的答案更改为指向首先指出此行为被认为是标准中的缺陷并已在现代编译器中修复的答案。
【解决方案2】:

这被认为是标准中的一个缺陷,并在 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 被用作 std::unordered_set 中的键,为 Visual Studio 2013 编译。
  • @ypnos:这很可能是有意的修复。它认为这在 Visual Studio 2012 中不起作用,因此他们可能在 2013 年将其作为该缺陷的一部分进行了修复。事实上,微软的 C++ 标准库维护者 STL 就是提供解决该缺陷的措辞的人。
  • @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 特性的一个子集。
  • 相信我,我不只是向你抛出一个链接——首先,页面确实描述了 C++14,只需向上滚动。其次,“对垃圾收集的最小支持” 符合标准。如果您阅读规范(链接在那里!):“不支持垃圾收集并实现此处描述为无操作的所有库调用的实现是符合要求的。”这就是 GCC 所做的。而且我看不出您为哪些平台编写代码或您觉得适合哪种编译器在讨论中增加了任何内容,这是关于标准一致性,而不是关于个人感知的编译器质量。
【解决方案3】:

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

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

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

【讨论】:

  • 这适用于老式枚举,但不适用于 OP 正在使用的新热点“枚举类”。
  • 当它公然不回答问题时,它是如何达到 +8 的?
【解决方案4】:

将此添加到定义 MyEnumClass 的标题中:

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

【讨论】:

  • 你不应该在签名中添加const noexcept吗?
  • 不幸的是,扩展 std 是未定义的行为。
  • @VictorPolevoy :对于这种特殊情况,幸运的是,扩展 std: 是定义的行为。 en.cppreference.com/w/cpp/language/extending_std
  • 我认为 llvm 8.1 对此有问题,但在其他编译器上工作正常。
【解决方案5】:

正如 KerrekSB 指出的那样,如果您想使用 std::unordered_map,则需要提供 std::hash 的专门化,例如:

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

【讨论】:

    【解决方案6】:

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

    【讨论】:

    • std::unordered_map 在几乎所有情况下都具有卓越的性能,并且可能应该被视为比std::map 更多的默认值。
    【解决方案7】:

    试试

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

    【讨论】:

    • 行不通。 std::hash() 将期望 underlying_type 的实例作为参数,但会得到 MyEnumClass。这与您尝试使用指定std::hash&lt;int&gt; 的旧的普通enum 解决方案时发生的事情完全相同。 在建议之前尝试过吗?
    • 当然,我做到了。在 VS 2012 中编译良好。正是这个“命名空间 ObjectDefines { enum ObjectType { ObjectHigh, .... } } std::unordered_map<:objecttype objectdata std::hash>::type>> m_mapEntry;"
    • 问题是关于enum class,而不是C 风格的无范围enums。你会看到我的评论对 enum class 是正确的,这是主题。
    • 我看到了不同之处。但它仍然编译得很好: class ObjectDefines { public: enum class ObjectType { ObjectHigh, ObjectLow }; }; std::unordered_map<:objecttype objectdefines std::hash>::type>> m_mapEntry;
    • 在 gcc 4.7.3 中也可以编译(在此处查看 melpon.org/wandbox/permlink/k2FopvmxQeQczKtE
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-11-22
    • 2019-06-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多