93

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?

Appleshell
  • 7,088
  • 6
  • 47
  • 96

8 Answers8

130

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.

Daniel
  • 2,657
  • 1
  • 17
  • 22
  • 1
    Simplest maybe, but just getting enum keys working in the first place must be simpler? :-S – Jonny Jan 04 '16 at 05:20
  • "Theoretically", no, `underlying_type` will not work. You showed yourself: there must be a `hash()` function that takes a `MyEnumClass` parameter. So, of course, `hash`ing on `underlying_type` invokes a function that expects an `int` (or `: yourEnumClassType`). Just trying `underlying_type` would've shown that it gives exactly the same error: cannot convert `MyEnumClass` to `int`. If just passing `underlying_type` _did_ work, **so** would passing `MyEnumClass` directly in the first place. Anyway, as David S shows, this has now been fixed by the WG. If only GCC would ever release their patch... – underscore_d Jan 11 '16 at 18:06
  • This didn't work for me in case the enum class was a protected "member" of another class. I needed to move the enum definition out of the class directly inside a namespace definition. – Martin Pecka Mar 21 '17 at 16:47
  • 1
    for some reasons I'm having no problems at all using an enum class as a a key in unordered map. i use clang, maybe support depends on compiler? edit : as another answer points out, this is in the standard as of c++14 – johnbakers May 02 '17 at 09:56
  • 1
    The accepted answer should be changed to point to the answer that starts out by pointing out that this behavior was considered a defect in the standard and has been fixed in modern compilers. – mallwright Oct 03 '19 at 12:46
  • This solution is bad as it unnecessarily creates a copy of the key. This will cause performance issues unless you use a reference. – Sooth Aug 06 '20 at 12:51
  • I have a fear that in case 2 or more enums are defined to have the same value it will cause a logical problem. Is that correct? – didinino Aug 09 '20 at 15:10
62

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

David Stone
  • 26,872
  • 14
  • 68
  • 84
  • 1
    I'm surprised `enum class` used as key in `std::unordered_set` compiles for Visual Studio 2013. – Richard Dally Aug 09 '15 at 22:04
  • I'm not surprised. All kinds of non-standard shit compiles in Visual Studio, it has been like this since ages. – ypnos Aug 21 '15 at 12:22
  • 3
    @ypnos: It was most likely an intentional fix. It believe this does not work in Visual Studio 2012, so they probably fixed it in 2013 as part of that defect. In fact, STL, the C++ standard library maintainer at Microsoft, is the one who provided the wording to resolve the defect. – David Stone Aug 21 '15 at 14:41
  • I'm not saying it is bad that they fixed it or that their compiler does random things. I'm just saying that Microsoft always provides a kind of limbo state of standards compliance, where some parts of the newest standard are supported, others aren't, and some parts of upcoming standards are supported early. It makes it much harder to write standard compliant code that also compiles with other toolchains. In GCC or clang you can specify which standard version you are programming against. But Windows programmers don't say "I program C++-11" or "I program C89". They say "I program VS 2012"… – ypnos Aug 21 '15 at 17:12
  • 1
    @ypnos: I prefer the Visual Studio approach of just putting everyone on the newest version of the language, as C++ *is* C++14 right now. – David Stone Oct 23 '15 at 02:02
  • 2
    @DavidStone: As a matter of fact, the current Visual Studio does not support C++14 or even C++11. They both include C99, which is not supported by Visual Studio. I'm not saying VS should not default to the newest language version. I'm saying it should not introduce its own de-facto standard when there is certain standards available that are supported by all competing compilers. VS 2013 came out a full year before C++14 was finalized. Yet, instead of fully supporting C++11 it includes a subset of C++14 features. – ypnos Oct 23 '15 at 08:34
  • @DavidStone Vladimir's answer below indicates that VS2012 does support this (or some variation) in `unordered_map`, however right or wrong it might be to do so. – underscore_d Jan 11 '16 at 21:06
  • 1
    @ypnos Yeah, well, as shown above gcc doesn't implement the full standard either, so it's pot-kettle-black. – quant_dev Mar 22 '16 at 11:37
  • @quant_dev What are you talking about? GCC has full conformance of all C++ standards, see https://gcc.gnu.org/projects/cxx-status.html#cxx11. While GCC developers also need time to implement new standards, they strive for 100% standard confirmance and they have a record of achieving it. It is the opposite of Microsoft, who deliberately not go for standards conformance, but, as they put it, "features our customers ask for". Also, my main point was: In GCC, Clang etc. I can choose a standards version I program against, and get expected behavior with _all_ recent enough versions of the compiler. – ypnos Mar 22 '16 at 13:24
  • 1
    @ypnos You page does not describe C++14 at all and 1 C++11 feature is missing ("Minimal support for garbage collection and reachability-based leak detection N2670 No"). – quant_dev Mar 22 '16 at 15:22
  • And actually, as a C++ developer who writes code for both Linux and Windows, I find Visual Studio C++ to be a very good, comfortable to use compiler. In some areas it's worse than GCC, in some it's better. – quant_dev Mar 22 '16 at 15:24
  • 2
    Believe me, I didn't just throw a link at you- First, the page *does* describe C++14, just scroll up. Second, the "Minimal support for garbage collection" *is* standards-conforming. If you read the spec (linked there!): "An implementation that does not support garbage collection and implements all library-calls described here as no-ops is conforming." This is what GCC does. And I don't see how the platforms you write code for or which compiler you find comfortable adds anything to the discussion, which was about **standards conformance** and not about individually perceived compiler quality. – ypnos Mar 22 '16 at 19:27
  • In gcc version 9.1.0 (where C++11 and C++14 features are enabled by default), the code compiles without any warnings or errors. – mallwright Oct 03 '19 at 12:44
  • @allsey87: Updated answer to reflect that gcc fixed this in 6.1 – David Stone Oct 04 '19 at 00:41
27

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.

denim
  • 1,329
  • 1
  • 15
  • 24
7

Add this to header defining MyEnumClass:

namespace std {
  template <> struct hash<MyEnumClass> {
    size_t operator() (const MyEnumClass &t) const { return size_t(t); }
  };
}
o9000
  • 1,626
  • 13
  • 15
user3080602
  • 307
  • 3
  • 4
5

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 ) );
        }
    };
}
Daniel Frey
  • 55,810
  • 13
  • 122
  • 180
4

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?

CS Pei
  • 10,869
  • 1
  • 27
  • 46
  • 34
    `std::unordered_map` has superior performance in almost all situations, and should probably be considered more of a default than `std::map`. – David Stone Apr 14 '15 at 03:13
0

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.

Fengggli
  • 123
  • 1
  • 10
-1

Try

std::unordered_map<Shader::Type, Shader, std::hash<std::underlying_type<Shader::Type>::type>> shaders;
Vladimir Shutow
  • 1,028
  • 1
  • 14
  • 22
  • Won't work. That `std::hash()` will expect an instance of `underlying_type` as a parameter but will get `MyEnumClass` instead. This is exactly the same thing that happens when you try to use the old plain `enum` solution of specifying `std::hash`. Did _you_ try this before suggesting it? – underscore_d Jan 11 '16 at 17:20
  • sure, I did. Compiles fine in VS 2012. Exactly this "namespace ObjectDefines { enum ObjectType { ObjectHigh, .... } } std::unordered_map::type>> m_mapEntry;" – Vladimir Shutow Jan 11 '16 at 20:19
  • The question is about `enum class`, not C-style unscoped `enum`s. You'll see that my comment is true for `enum class`, which is the subject. – underscore_d Jan 11 '16 at 20:45
  • I see the difference. But it still compiles fine: class ObjectDefines { public: enum class ObjectType { ObjectHigh, ObjectLow }; }; std::unordered_map::type>> m_mapEntry; – Vladimir Shutow Jan 11 '16 at 20:55
  • Interesting! Based on other answers, especially the one citing the Standard defect, I'm pretty sure this **shouldn't** work... (& I don't know how they would've implemented it.) Like my earlier test, the same code into GCC (with `#include ` added) produces a classic pile of templated error messages - of which the first & most useful is `/usr/include/c++/5/bits/hashtable_policy.h:85:34: error: no match for call to ‘(const std::hash) (const ObjectDefines::ObjectType&)’ `. LLVM/`clang++` agrees. Can anyone confirm whether VS2012 is wrong or right, & maybe test the latest ver? – underscore_d Jan 11 '16 at 21:01
  • 1
    in gcc 4.7.3 it compiles too (checked here http://melpon.org/wandbox/permlink/k2FopvmxQeQczKtE) – Vladimir Shutow Jan 11 '16 at 21:26
  • Yeah, but not in 5.2.0 available on the same page, or in the latest v4 version 4.9.2. This just gets weirder... – underscore_d Jan 11 '16 at 21:31