In the realm of C standard libraries, the concept of tuples and their usage as keys in unordered collections like std::unordered_map and std::unordered_set can pose a challenge. By default, tuples do not have a generic hash function defined, leaving developers with the tedious task of manually defining one.
Defining a custom hash function for tuples can be cumbersome and prone to error. To address this issue, developers often seek a more generic solution that automates the process.
While the standard does not explicitly provide a generic hash function for tuples, a standards-compliant approach is available. By moving the code into a custom namespace, it is possible to avoid undefined behavior associated with specializing in the std namespace.
In this approach, a custom namespace, hash_tuple, is created with its own implementation of the hash function. This implementation dispatches non-tuple types to the std::hash function.
namespace hash_tuple{ template <typename TT> struct hash { size_t operator()(TT const& tt) const { return std::hash<TT>()(tt); } }; }
The recursive template code is modified to utilize hash_tuple::hash instead of std::hash:
namespace hash_tuple{ namespace { template <class T> inline void hash_combine(std::size_t& seed, T const& v) { seed ^= hash_tuple::hash<T>()(v) + 0x9e3779b9 + (seed<<6) + (seed>>2); } } }
Finally, the std template specialization is placed within the hash_tuple namespace:
namespace hash_tuple{ template <typename ... TT> struct hash<std::tuple<TT...>> { size_t operator()(std::tuple<TT...> const& tt) const { size_t seed = 0; HashValueImpl<std::tuple<TT...> >::apply(seed, tt); return seed; } }; }
To use this approach, users must specify the hash_tuple namespace in their unordered collection declarations:
unordered_set<tuple<double, int>, hash_tuple::hash<tuple<double, int>>> test2;
While this solution is standards-compliant, it requires specifying the namespace for each unordered collection declaration.
An alternative approach, which is not compliant with the C standard, is to place the generic hash function code in the std namespace. This allows argument-dependent lookup (ADL) to automatically find the correct hash implementation.
namespace std{ namespace { // Code from boost // Reciprocal of the golden ratio helps spread entropy // and handles duplicates. // See Mike Seymour in magic-numbers-in-boosthash-combine: // http://stackoverflow.com/questions/4948780 template <class T> inline void hash_combine(std::size_t& seed, T const& v) { seed ^= std::hash<T>()(v) + 0x9e3779b9 + (seed<<6) + (seed>>2); } // Recursive template code derived from Matthieu M. template <class Tuple, size_t Index = std::tuple_size<Tuple>::value - 1> struct HashValueImpl { static void apply(size_t& seed, Tuple const& tuple) { HashValueImpl<Tuple, Index-1>::apply(seed, tuple); hash_combine(seed, std::get<Index>(tuple)); } }; template <class Tuple> struct HashValueImpl<Tuple,0> { static void apply(size_t& seed, Tuple const& tuple) { hash_combine(seed, std::get<0>(tuple)); } }; } template <typename ... TT> struct hash<std::tuple<TT...>> { size_t operator()(std::tuple<TT...> const& tt) const { size_t seed = 0; HashValueImpl<std::tuple<TT...> >::apply(seed, tt); return seed; } }; }
With this approach, unordered collection syntax remains simpler:
unordered_set<tuple<double, int> > test_set;
However, this technique carries the risk of undefined behavior due to the specialization in the std namespace.
The generic hashing of tuples in unordered collections is a non-trivial problem that can require a custom implementation. Both the standards-compliant and non-standard approaches outlined in this article provide viable solutions. Ultimately, the choice between these approaches depends on the developer's requirements and tolerance for potential undefined behavior.
The above is the detailed content of How can you implement a generic hash function for tuples in unordered collections in C ?. For more information, please follow other related articles on the PHP Chinese website!