std::unordered_map error

Hey,

Given these definitions:
1
2
enum class ExitDirection {NORTH, SOUTH, EAST, WEST};
using RoomNumber = int;


and this declaration:
std::unordered_map<ExitDirection, RoomNumber> exits;

Why would I get this error?
/Library/Developer/CommandLineTools/usr/include/c++/v1/type_traits:1464:38: error: implicit instantiation of undefined template
'std::__1::hash<Room::ExitDirection>'
: public integral_constant<bool, __is_empty(_Tp)> {};
^
/Library/Developer/CommandLineTools/usr/include/c++/v1/unordered_map:383:18: note: in instantiation of template class
'std::__1::is_empty<std::__1::hash<Room::ExitDirection> >' requested here
bool = is_empty<_Hash>::value && !__libcpp_is_final<_Hash>::value
^
/Library/Developer/CommandLineTools/usr/include/c++/v1/unordered_map:765:13: note: in instantiation of default argument for
'__unordered_map_hasher<Room::ExitDirection, std::__1::__hash_value_type<Room::ExitDirection, int>, std::__1::hash<Room::ExitDirection> >'
required here
typedef __unordered_map_hasher<key_type, __value_type, hasher> __hasher;
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
./Room.h:36:8: note: in instantiation of template class 'std::__1::unordered_map<Room::ExitDirection, int, std::__1::hash<Room::ExitDirection>,
std::__1::equal_to<Room::ExitDirection>, std::__1::allocator<std::__1::pair<const Room::ExitDirection, int> > >' requested here
Exits exits;


Note that this works:
std::unordered_map<int, RoomNumber> exits;

and this does not:
std::unordered_map<const int, RoomNumber> exits;

I can deduce a couple of things from those assertions, like there is some operation that needs to be performed on keys that require non-const, but I'm not sure what the best way to work around that is.
So, the issue is that there is no specialisation of std::hash for your enum type; this is a requirement of std::unordered_map. That said, I'm pretty sure that that's a bug in the C++11 standard, and was fixed in C++14, where std::hash now works as expected for enum too.

If you're unwilling or unable to migrate to C++14, you can either provide your own specialization of std::hash, or provide std::unordered_map your own hashing function object:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// standalone function object
struct ExitHash {
  std::size_t operator()(ExitDirection e) const {
    return static_cast<std::size_t>(e);
  }
};

// pass the hash object as a third template parameter
// you could also hide this behind a using-declaration
std::unordered_map<ExitDirection, int, ExitHash> exits;

// OR

// a custom specialisation (and yes, you are allowed (and have to) put this in std)
namespace std {
  template<>
  struct hash<ExitDirection> {
    using argument_type = ExitDirection;
    using result_type = std::size_t;
    
    result_type operator()(argument_type a) const {
      return static_cast<result_type>(a);  
    }
  };
}

// should just work
std::unordered_map<ExitDirection, int> exits;


As an aside, the reason that const int didn't work is again that there was no specialisation of std::hash for it, not that there is any modification being performed on the key type. Then again, you aren't allowed to modify the keys in a map anyway, so the const is extraneous.

EDIT: As an aside, those are fairly lazy hash functions there; if you want to do it properly you should be using std::underlying_type to get the actual type of the enum, whereas I just willy-nilly cast it to std::size_t as my "hash function". In practice it's usually good enough.
http://coliru.stacked-crooked.com/a/fc492702f5c52e98
Last edited on
Topic archived. No new replies allowed.