一般不能将函数/类注入namespace std
,但模板函数对象类std::hash
是一个例外
为了能使用std::pair / std::tuple
作为哈希表的键值与哈希集合的元素,需要特化std::hash
函数对象(operator()
返回std::size_t
的哈希值),std::pair / std::tuple
包含了多个元素,参与到哈希值计算中的元素都相同时,哈希值相同,所以每个元素都应该参与到哈希值计算中。
采用boost::hash_combine
的实现,先定义一个辅助函数组合总体哈希值与某一元素的哈希值
namespace std {
namespace {
template <typename T> inline void hash_combine(std::size_t &seed, T const &v) {
seed ^= hash<T>()(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
}// namespace
}// namespace std
std::pair
的哈希特化是简单的,因为std::pair
定长为2
namespace std {
template <typename U, typename V> struct hash<std::pair<U, V>> {
size_t operator()(std::pair<U, V> const &tt) const {
size_t seed = 0;
hash_combine(seed, tt.first);
hash_combine(seed, tt.second);
return seed;
}
};
} // namespace std
对于变长的std::tuple
,可以使用std::apply
解构std::tuple
,std::apply
会将tuple
中每一个元素完美转发到f
的形参上,而f
是std::apply
第一个实参。f
参数长度不确定,可以使用变长模板参数的lambda
来接tuple
中的元素...x
,函数体内部再用折叠表达式展开一个逗号表达式,其子表达式调用hash_combine(seed, x)
即可
namespace std {
template <typename... TT> struct hash<std::tuple<TT...>> {
size_t operator()(std::tuple<TT...> const &tt) const {
size_t seed = 0;
std::apply([&seed](auto &&...x) { ((hash_combine(seed, x)), ...); }, tt);
return seed;
}
};
} // namespace std
完整代码
#include <tuple>
namespace std {
namespace {
template <typename T> inline void hash_combine(std::size_t &seed, T const &v) {
seed ^= hash<T>()(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
} // namespace
template <typename... TT> struct hash<std::tuple<TT...>> {
size_t operator()(std::tuple<TT...> const &tt) const {
size_t seed = 0;
std::apply([&seed](auto &&...x) { ((hash_combine(seed, x)), ...); }, tt);
return seed;
}
};
template <typename U, typename V> struct hash<std::pair<U, V>> {
size_t operator()(std::pair<U, V> const &tt) const {
size_t seed = 0;
hash_combine(seed, tt.first);
hash_combine(seed, tt.second);
return seed;
}
};
} // namespace std
使用例
#include <iostream>
#include <unordered_map>
#include <unordered_set>
int main() {
std::unordered_set<std::pair<std::string, int>> h1{
{"java", 18}, {"golang", 105}, {"cpp", 23}, {"rust", 172}};
std::unordered_map<std::tuple<float, std::string, int>, int> h2{
{{1.8, "java", 18}, -1}, {{1.05, "golang", 105}, -2}};
for (auto && v: h1)
std::cout << v.first << ", " << v.second << '\n';
}