Question

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?

Was it helpful?

Solution

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;

So you don't need to provide a specialization of std::hash, the template argument deduction does the job. 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;

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.

OTHER TIPS

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

A very simple solution would be to provide a hash function object like this:

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

That's all for an enum key, no need to provide a specialization of std::hash.

Add this to header defining MyEnumClass:

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

As KerrekSB pointed out, you need to provide a specialization of std::hash if you want to use std::unordered_map, something like:

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

When you use std::unordered_map, you know you need a hash function. For built-in or STL types, there are defaults available, but not for user-defined ones. If you just need a map, why don't you try std::map?

I met similar issues when I wanted to get a unordered_map from enum type to string.

You most probably don't need unordered_map. Because you use enum class as your key, I assume that you don't need operations like insert or remove: simple lookup will be sufficient.

That being said, why not just use a function instead?

// assume shader_vertex, shader_geometry, shader_fragment are initialized
Shader* toShader(Shader::Type value){
  switch(value){
    case Shader::Type::Vertex:
      return &shader_vertex;
    case Shader::Type::Geometry:
      return &shader_geometry;
    case Shader::Type::Fragment:
      return &shader_fragment;
    default:
      //some error handling..
      return nullptr;
}

Unlike other solutions, this approach is compiler-agnostic.

Credits go to this project link.

Try

std::unordered_map<Shader::Type, Shader, std::hash<std::underlying_type<Shader::Type>::type>> shaders;
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top