Skip to content

enum as key

enum class as key

stackoverflow Can't use enum class as unordered_map key

Q

I have a class containing an enum class.

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

Then, when I implement the following code in another class...

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

...I get a compile error.

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

What is causing the error here?

NOTE: 完整测试程序如下:

#include <unordered_map>
/**
 * @brief 着色器
 *
 */
class Shader
{
public:
  enum class Type
  {
      Vertex = 1, Geometry = 2, Fragment = 3
  };
  //...
};

int main()
{
  std::unordered_map<Shader::Type, Shader> shaders;
}
// g++ --std=c++11 test.cpp

编译报错如下:

/usr/include/c++/4.8.2/bits/hashtable_policy.h:1070:12: error: invalid use of incomplete type struct std::hash<Shader::Type>
     struct _Hash_code_base<_Key, _Value, _ExtractKey, _H1, _H2,

A

I use a functor object to calculate hash of enum class:

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

Now you can use it as 3rd template-parameter of std::unordered_map:

enum class MyEnum {};

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

NOTE: 完整测试程序如下:

#include <unordered_map>
#include <iostream>

/**
 * @brief 着色器
 *
 */
class Shader
{
public:
  enum class Type
  {
      Vertex = 1, Geometry = 2, Fragment = 3
  };
  //...
};

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

int main()
{
  std::unordered_map<Shader::Type, Shader, EnumClassHash> shaders;
  shaders[Shader::Type::Vertex] = Shader();
  shaders[Shader::Type::Geometry] = Shader();
}
// g++ --std=c++11 test.cpp

输出如下:

test.cpp 23 
test.cpp 23 

可以看到,EnumClassHashoperator()(T t)会被调用。

上述例子将EnumClassHash来作为hash function,可以看到,EnumClassHash仅仅将enum的值作为hash value,显然这不是一个好的hash function。

So you don't need to provide a specialization of std::hash, the template argument deduction does the job.

NOTE: 这段话的意思是: 对于function template,C++ compiler会自动进行template argument deduction。

Furthermore, you can use the word using and make your own unordered_map that use std::hash or EnumClassHash depending on the 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>>;

Now you can use MyUnorderedMap with enum class or another type:

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

NOTE: 完整测试程序如下:

#include <unordered_map>
#include <iostream>
#include <type_traits>

/**
 * @brief 着色器
 *
 */
class Shader
{
public:
  enum class Type
  {
      Vertex = 1, Geometry = 2, Fragment = 3
  };
  //...
};

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

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

int main()
{
  MyUnorderedMap<int, int> myMap2;
  MyUnorderedMap<Shader::Type, int> myMap3;
}
// g++ --std=c++11 test.cpp

Theoretically, HashType could use std::underlying_type and then the EnumClassHash will not be necessary. That could be something like this, but I haven't tried yet:

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

If using std::underlying_type works, could be a very good proposal for the standard.

NOTE: 完整测试程序如下:

#include <unordered_map>
#include <iostream>
#include <type_traits>

/**
 * @brief 着色器
 *
 */
class Shader
{
public:
  enum class Type
  {
      Vertex = 1, Geometry = 2, Fragment = 3
  };
  //...
};

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

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

template<typename Key, typename T>
using MyUnorderedMap = std::unordered_map<Key, T, HashType<Key>>;

int main()
{
  MyUnorderedMap<int, int> myMap2;
  MyUnorderedMap<Shader::Type, int> myMap3;
}
// g++ --std=c++11 test.cpp

上述程序编译报错如下:

est.cpp:30:111: error: type/value mismatch at argument 1 in template parameter list for template<class _Tp> struct std::hash
 using HashType = typename std::conditional<std::is_enum<Key>::value, std::hash<std::underlying_type<Key>::type>, std::hash<Key>>::type;

A

This was considered a defect in the standard, and was fixed in C++14: http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#2148

This is fixed in the version of libstdc++ shipping with gcc as of 6.1: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60970.

It was fixed in clang's libc++ in 2013: http://lists.cs.uiuc.edu/pipermail/cfe-commits/Week-of-Mon-20130902/087778.html

cplusplus enum class as map key