So your basic problem is that you don't have a hasher for std::tuple
-- the standard library does not currently come with one by default. I think this is an oversight.
You cannot add a default hasher for std::tuple
-- either for all tuple
s or for your particular list -- because the standard says you aren't allowed to:
17.6.4.2.1 Namespace std [namespace.std]/1 The behavior of a C++ program is undefined if it adds declarations or definitions to
namespace std or to a namespace within namespace std unless otherwise
specified. A program may add a template specialization for any
standard library template to namespace std only if the declaration
depends on a user-defined type and the specialization meets the
standard library requirements for the original template and is not
explicitly prohibited.
So this leaves you with either writing a custom hasher for your tuple
type, and passing it to your unordered_map
, or using a type that isn't merely std::tuple
for your key, or writing a custom hasher that supports every tuple
(including recursive tuple
s) while also supporting hash<T>
lookup for non-tuple
s. A 4th option would be to write your own ADL-based hash system and set it up so that types with valid std::hash
specializations use that, with various other fallbacks.
I will illustrate the 3rd option above.
First, you want to be able to combine two hash values in a way that doesn't lead to many spurious collisions.
inline std::size_t hash_result_combine(std::size_t lhs, std::size_t rhs)
{
return lhs ^( rhs + 0x9e3779b9 + (lhs<<6) + (lhs>>2));
}
this takes two hash
outputs, and combines them. We can then chain this any number of times. (constants borrowed from https://stackoverflow.com/a/7111460/1774667 )
Next, some indexing boilerplate. Most generic work with std::tuple
requires this stuff:
template<unsigned...>struct indexes{};
template<unsigned Max, unsigned... Is>struct make_indexes:make_indexes<Max-1,Max-1,Is...> {};
template<unsigned...Is>struct make_indexes<0,Is...>:indexes<Is...>{};
Finally, a hasher class that works with nearly every type:
struct my_hasher {
template<typename... Args>
std::size_t operator()(indexes<>, std::tuple<Args...> const& tup) const {
return 0;
}
template<unsigned I, unsigned... Is,typename... Args>
std::size_t operator()(indexes<I, Is...>,std::tuple<Args...> const& tup) const {
return hash_result_combine( (*this)(std::get<I>(tup)), (*this)(indexes<Is...>, tup) );
}
template<typename... Args>
std::size_t operator()(std::tuple<Args...> const& tup) const {
return (*this)(make_indexes<sizeof...(Args)>(), tup);
}
template<typename T>
std::size_t operator()(T const& t) const { return std::hash<T>()(t); }
};
you can pass my_hasher
to an unordered_set
in place of hash<T>
for tuple
s, tuple
s containing tuples, or even for scalars -- it is very generous.
typedef std::unordered_map<const key_t,IndexGuide, my_hasher> GuideDouble;
and now we properly hash key_t
.