所以你的基本问题是你没有一个哈希器std::tuple
——标准库目前默认没有一个。我认为这是一个疏忽。
您不能std::tuple
为所有tuple
s 或您的特定列表添加默认哈希,因为标准规定您不允许:
17.6.4.2.1命名空间 std [namespace.std]/1 如果 C++ 程序向命名空间 std 或命名空间 std 内的命名空间添加声明或定义,则其行为未定义,除非另有说明。只有当声明依赖于用户定义的类型并且特化满足原始模板的标准库要求并且没有明确禁止时,程序才能将任何标准库模板的模板特化添加到命名空间 std。
因此,这使您可以为您的类型编写自定义散列器tuple
,并将其传递给您的unordered_map
,或者使用不仅std::tuple
用于您的键的类型,或者编写支持每个tuple
(包括递归tuple
)同时还支持hash<T>
查找的自定义散列器对于非tuple
s。第四种选择是编写您自己的基于 ADL 的哈希系统并对其进行设置,以便具有有效特化的类型std::hash
使用它,以及各种其他后备。
我将说明上面的第三个选项。
首先,您希望能够以一种不会导致许多虚假冲突的方式组合两个哈希值。
inline std::size_t hash_result_combine(std::size_t lhs, std::size_t rhs)
{
return lhs ^( rhs + 0x9e3779b9 + (lhs<<6) + (lhs>>2));
}
这需要两个hash
输出,并将它们组合起来。然后我们可以将其链接任意次数。(从https://stackoverflow.com/a/7111460/1774667借来的常数)
接下来,一些索引样板。大多数通用工作都std::tuple
需要这些东西:
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...>{};
最后,一个适用于几乎所有类型的哈希类:
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); }
};
你可以传递my_hasher
给一个unordered_set
代替hash<T>
for tuple
s,tuple
s 包含元组,甚至是标量 - 这是非常慷慨的。
typedef std::unordered_map<const key_t,IndexGuide, my_hasher> GuideDouble;
现在我们正确地散列key_t
。