std::hash<T>
has no std::pair<X,Y>
specialization. You have to provide one.
Boost's one isn't as bad as the first one you are likely to write, so feel free to use it. (It isn't perfect, but it isn't horrid).
The hashing trick I use is to first start with an ADL helper:
namespace MyNS {
template<class T, class...Unused>
std::size_t hash( T const& t, Unused... )->decltype( std::hash<T>{}(std::declval<T const&>()) ) {
return std::hash<T>{}(t);
}
struct hasher {
template<class T>
std::size_t operator()(T const& t)const {
return hash(t);
}
}
}
as written, MyNS::Hasher
passed to a std::unordered_map<X,Y, MyNS::Hasher>
will invoke std::hash<X>
as its hasher.
But if you override hash
in the namespace of X
or in MyNS
you'll call it instead. This allows us to extend stuff nicely.
namespace MyNS {
std::size_t hash_combine( std::size_t lhs, std::size_t rhs ) {
return ((lhs<<6)+(lhs>>3)) + rhs;
}
template<class...Szs>
std::size_t hash_combine( std::size_t lhs ) {
return lhs;
}
template<class...More>
std::size_t hash_combine( std::size_t lhs, std::size_t rhs, More...more ) {
return hash_combine( hash_combine(lhs, rhs), more... );
}
template<class T, class U>
std::size_T hash( std::pair<T, U> const& p ) {
return hash_combine( hash(p.first), hash(p.second) );
}
}
I then add this. hash_combine
can be written better (boost has an example of a better one), but it combines two size_t hashes into one. But the above one is better than nothing.
It takes 1 or more arguments and combines their hashes into one hash.
I then add an overload to hash
for std::pair
It invokes hash
on its elements; this means pairs of pairs are supported. We can add tuple support:
template<std::size_t...Is, class Tup>
std::size_T hash( std::index_sequence<Is...>, Tup const& tup ) {
return hash_combine( std::get<Is>(tup)... );
}
template<class...Ts>
std::size_T hash( std::tuple<Ts...> const& tup ) {
return hash_combine( std::make_index_sequence<sizeof...(Ts)>{}, tup );
}
and vector support:
template<class Seq>
std::size_t hash_sequence(Seq const& v) {
std::size_t r = 0;
for (auto const& e:v) {
r = hash_combine( r, hash(e) );
}
return r;
}
template<class T, class A>
std::size_t hash(std::vector<T,A> const& v) {
return hash_sequence(v);
}
and similar for other containers.
Any class you need to support hash for is easy:
namespace SomeRandomNS {
struct some_random_type {
int state;
};
inline std::size_t hash( some_random_type const& x ) {
using MyNS::hash;
return hash(x.state);
}
}
and done. For something more complex:
namespace SomeRandomNS {
struct some_random_type {
int state;
double more_state;
std::string even_more;
};
inline std::size_t hash( some_random_type const& x ) {
using MyNS::hash;
return hash(std::tie(x.state, x.more_state, x.even_more));
}
}
and we are done (the tuple hash does the work for us).