Is specializing std::hash
outside namespace std
unsafe?
No. template<> class std::hash<Sales_data> { ... };
in the global namespace will result in the same as
namespace std {
template<>
class hash<Sales_data> { ... };
}
in the global namespace.
Shouldn't I do this or it is erroneous to do so?
It's fine to do so - but older versions of g++ (versions before version 7 I think, which are still in use) will produce a warning along the lines "warning: specialization of template in different namespace" if you do it like you do in the question. Combined with -Werror
this could cause perfectly correct code to fail to compile. I suggest using the namespace { ... }
version.
A note on the hash itself: std::hash<any_type_smaller_or_of_equal_size_of_size_t>
is likely to be a very poor hash (changing 1 bit won't flip approximately half of the others) in the standard library, returning the same output (bitpattern) as it got as input. This is common because it's extremely fast and fulfills all that's needed when used as a key in a standard container. Every value will get a unique hash, so it's just perfect from that point of view. The problem comes when combining these together. Collisions will be very easy to create - and you want to keep collisions down to a minimum to make lookups in your unordered
containers fast. Your XOR
operations doesn't change the fact about collisions being plentiful, so I suggest using a function to combine hashes. boost
has one that is commonly used (boost::hash_combine
).
It looks something like this for 32 bit size_t
s (not sure if it's different in different boost
versions):
template <class T>
inline void hash_combine(std::size_t& seed, const T& v)
{
std::hash<T> hasher;
seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
Example:
namespace stolen { // from boost - use the real boost version to get proper 64 bit hashes
template <class T>
inline void hash_combine(std::size_t& seed, const T& v)
{
std::hash<T> hasher;
seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
}
namespace std {
template <>
class hash<Sales_data> {
public:
using result_type = std::size_t;
using argument_type = Sales_data;
result_type operator()(argument_type const& arg) const {
result_type result = hash<std::string>{}(arg.isbn_);
stolen::hash_combine(result, arg.nCopySold_);
stolen::hash_combine(result, arg.revenue_);
return result;
}
};
}
There's also a proposal to add some version of hash_combine
into the standard library: hash_combine()
Again