I'm not a pro in C++ but somehow I provided a solution while porting my MSVS 2015 C++ code to MinGW 4.9.2 to specialize std::hash
class to support all enum
s. It there's any C++ compiler developers or C++ pro programmers here, can you explain why this specialization works, although it is Undefined Behavior according the C++ standard they say?
Link for full code with samples on Gist
#include <unordered_set>
#include <functional>
#if (defined __GNUC__) && (__GNUC__ < 6)
// Adds support of std::hash<enum T> to libstdc++.
// GCC 6 provides it out of the box.
namespace std {
template<typename T>
struct hash {
constexpr size_t operator()(typename std::enable_if<std::is_enum<T>::value, T>::type s) const noexcept {
return static_cast<size_t>(s);
}
};
}
#endif
Providing support for std::hash<enum T>
means that all classes like std::unordered_XXX
will support any enum
as a key.
GCC 6.1.0 with this std::hash
definition fails with error
error: redefinition of 'struct std::hash<_Tp>'
GCC 5.3.0 without this std::hash
definition fails for std::unordered_set with following error:
In file included from /usr/local/gcc-5.3.0/include/c++/5.3.0/bits/hashtable.h:35:0,
from /usr/local/gcc-5.3.0/include/c++/5.3.0/unordered_set:47,
from prog.cc:1:
/usr/local/gcc-5.3.0/include/c++/5.3.0/bits/hashtable_policy.h: In instantiation of 'struct std::__detail::__is_noexcept_hash<Foo, std::hash<Foo> >':
/usr/local/gcc-5.3.0/include/c++/5.3.0/type_traits:137:12: required from 'struct std::__and_<std::__is_fast_hash<std::hash<Foo> >, std::__detail::__is_noexcept_hash<Foo, std::hash<Foo> > >'
/usr/local/gcc-5.3.0/include/c++/5.3.0/type_traits:148:38: required from 'struct std::__not_<std::__and_<std::__is_fast_hash<std::hash<Foo> >, std::__detail::__is_noexcept_hash<Foo, std::hash<Foo> > > >'
/usr/local/gcc-5.3.0/include/c++/5.3.0/bits/unordered_set.h:95:63: required from 'class std::unordered_set<Foo>'
prog.cc:25:25: required from here
/usr/local/gcc-5.3.0/include/c++/5.3.0/bits/hashtable_policy.h:85:34: error: no match for call to '(const std::hash<Foo>) (const Foo&)'
noexcept(declval<const _Hash&>()(declval<const _Key&>()))>
^
In file included from /usr/local/gcc-5.3.0/include/c++/5.3.0/bits/move.h:57:0,
from /usr/local/gcc-5.3.0/include/c++/5.3.0/bits/stl_pair.h:59,
from /usr/local/gcc-5.3.0/include/c++/5.3.0/utility:70,
from /usr/local/gcc-5.3.0/include/c++/5.3.0/unordered_set:38,
from prog.cc:1:
/usr/local/gcc-5.3.0/include/c++/5.3.0/type_traits: In instantiation of 'struct std::__not_<std::__and_<std::__is_fast_hash<std::hash<Foo> >, std::__detail::__is_noexcept_hash<Foo, std::hash<Foo> > > >':
/usr/local/gcc-5.3.0/include/c++/5.3.0/bits/unordered_set.h:95:63: required from 'class std::unordered_set<Foo>'
prog.cc:25:25: required from here
/usr/local/gcc-5.3.0/include/c++/5.3.0/type_traits:148:38: error: 'value' is not a member of 'std::__and_<std::__is_fast_hash<std::hash<Foo> >, std::__detail::__is_noexcept_hash<Foo, std::hash<Foo> > >'
: public integral_constant<bool, !_Pp::value>
...
This is an ill formed program with no diagnostic required because are not allowed to provide base specializations for template types in std
.
It worked because in some implementations, providing such a base specialization happens to redirect all std::hash<T>
without an explicit specialization to your hash implementation. Then your operator()
proceeds to only work with enum
s, but that is not why it works, nor does it prevent it from making your program ill formed with no diagnostic required.
Practically, it probably broke because someone SFINAE enabled std::hash
with an empty base implementation in gcc 6.1: this may be mandated by some C++ standard, unsure, but it is a quality of implementation improvement if not.
The right way to do this is to create
struct enum_hash {
template<typename T>
constexpr
typename std::enable_if<std::is_enum<T>::value,std::size_t>::type
operator()(T s) const noexcept {
return static_cast<std::size_t>(s);
}
};
Which is a type that can hash any enum.
Now pass ,enum_hash
to unordered_set<some_enum, enum_hash>
like that.