I was using an ordered set declared as so:
std::set<std::pair<const std::string, const myClass *> > myset;
After doing some analysis of the way I was using the set,
I concluded that an unordered_set
would be a smarter choice. But when I changed std::set
to std::unordered_set
, I got a vast spew of error messages from my compiler (g++ 4.8.1) complaining of an
invalid use of incomplete type struct std::hash<std::pair<const std::basic_string<char>, const myClass * > >
I figured out that std::hash
didn't know how to deal with a type which was std::pair
, despite the fact that the two types that made up the pair
were each hashable. I think error for hash function of pair of ints contains relevant information about the C++11 standard that explains why things went awry. (There's no good explanation for the impenetrable wall of error text that g++ emits for this.)
It would seem to me that
std::hash<std::pair<T1, T2>> hasher(make_pair(x,y))
= some_func(std::hash<T1>hasher(x), std::hash<T2>hasher(y) )
where some_func()
could be as simple as XOR (or not; see Why is XOR the default way to combine hashes?)
Is there a good reason for the standard to not require std::hash
to know how to construct a hash value for an object which is a pair
of types that are each hashable?
The reason is simple, it was not added to the standard. The same is true of hashing other structures like
tuple
.Things tend to be added to the standard when they are good enough, not when they are perfect, as perfection is the enemy of the good. More specializations of
std::hash
are not things that will break code (that often), so adding new ones is relatively harmless.In any case, to that end, we can write our own hash extenders. As an example:
and now
hashers::hash<T>
will recursively use either an ADL-looked upcustom_hash
function, orstd::hash
if that fails, to hashT
and its components, andhashers::hash<>
is a universal hasher that tries to hash anything passed to it.Code may not compile as shown.
I chose to hash all containers and tuples as hash their length, followed by hashing the combination of their contents. As a side effect,
array<int, 3>
hashes the same astuple<int,int,int>
, andtuple<int,int>
hashes the same aspair<int,int>
, andstd::vector<char>{'a','b','c', '\0'}
hashes the same as"abc"
, which I think is a nice property. The empty array/tuple/vector/etc hashes likesize_t(0)
.You can extend the above system for your own types by simply overriding
custom_hash
in the namespace of the type in question, or specializing eitherstd::hash<X>
orhashers::hash<X>
to do your custom hash (I would go withstd::hash
for the principle of least surprise myself). For advanced use, you can specializehashers::details::hash<X,void>
with SFINAE, but I'd say do it forcustom_hash
instead.