原因很简单,没有加入标准。散列其他结构(如 tuple)也是如此。
当事物足够好时,往往会被添加到标准中,而不是当它们完美时,因为完美是好的敌人。 std::hash 的更多特化不会(经常)破坏代码,因此添加新的相对无害。
无论如何,为此,我们可以编写自己的哈希扩展器。举个例子:
namespace hashers {
constexpr size_t hash_combine( size_t, size_t ); // steal from boost, or write your own
constexpr size_t hash_combine( size_t a ) { return a; }
constexpr size_t hash_combine() { return 0; }
template<class...Sizes>
constexpr size_t hash_combine( size_t a, size_t b, Sizes... sizes ) {
return hash_combine( hash_combine(a,b), sizes... );
}
template<class T=void> struct hash;
template<class A, class B>
constexpr size_t custom_hash( std::pair<A,B> const& p ) {
return hash_combine( hash<size_t>{}(2), hash<std::decay_t<A>>{}(p.first), hash<std::decay_t<B>>{}(p.second) );
}
template<class...Ts, size_t...Is>
constexpr size_t custom_hash( std::index_sequence<Is...>, std::tuple<Ts...> const& p ) {
return hash_combine( hash<size_t>{}(sizeof...(Ts)), hash<std::decay_t<Ts>>{}(std::get<Is>(p))... );
}
template<class...Ts>
constexpr size_t custom_hash( std::tuple<Ts...> const& p ) {
return custom_hash( std::index_sequence_for<Ts...>{}, p );
}
template<class T0, class C>
constexpr size_t custom_hash_container( size_t n, C const& c) {
size_t retval = hash<size_t>{}(n);
for( auto&& x : c)
retval = hash_combine( retval, hash<T>{}(x) );
return retval;
}
template<class T0, class C>
constexpr size_t custom_hash_container( C const& c) {
return custom_hash_container( c.size(), c );
}
template<class T, class...Ts>
size_t custom_hash( std::vector<T, Ts...> const& v ) {
return custom_hash_container<T>(v);
}
template<class T, class...Ts>
size_t custom_hash( std::basic_string<T, Ts...> const& v ) {
return custom_hash_container<T>(v);
}
template<class T, size_t n>
constexpr size_t custom_hash( std::array<T, n> const& v ) {
return custom_hash_container<T>(n, v);
}
template<class T, size_t n>
constexpr size_t custom_hash( T (const& v)[n] ) {
return custom_hash_container<T>(n, v);
}
// etc -- list, deque, map, unordered map, whatever you want to support
namespace details {
template<class T, class=void>
struct hash : std::hash<T> {};
using hashers::custom_hash;
template<class T>
struct hash<T,decltype(void(
custom_hash(declval<T const&>())
)) {
constexpr size_t operator()(T const& t)const {
return custom_hash(t);
}
};
}
template<class T>
struct hash : details::hash<T> {};
template<>
struct hash<void> {
template<class T>
constexpr size_t operator()(T const& t)const { return hash<T>{}(t); }
}
}
现在hashers::hash<T> 将递归地使用 ADL 查找的 custom_hash 函数或 std::hash 如果失败,对 T 及其组件进行散列,hashers::hash<> 是一个通用散列器,它试图散列任何传递给它的东西。
代码可能无法如图所示编译。
我选择散列所有容器和元组作为散列它们的长度,然后散列它们的内容组合。作为副作用,array<int, 3> 的哈希值与tuple<int,int,int> 相同,tuple<int,int> 的哈希值与pair<int,int> 相同,std::vector<char>{'a','b','c', '\0'} 的哈希值与"abc" 相同,我认为这是一个不错的属性。空数组/元组/向量/等哈希,如size_t(0)。
您可以通过简单地在相关类型的命名空间中覆盖custom_hash 来为您自己的类型扩展上述系统,或者专门使用std::hash<X> 或hashers::hash<X> 来执行您的自定义哈希(我会选择@987654340 @为了我自己最不惊讶的原则)。对于高级用途,您可以使用 SFINAE 专门化 hashers::details::hash<X,void>,但我会建议使用 custom_hash 来代替。