【发布时间】:2011-02-05 03:34:21
【问题描述】:
C++0x 添加hash<...>(...)。
我找不到hash_combine 函数,如boost 中所示。实现这样的事情的最干净的方法是什么?也许,使用 C++0x xor_combine?
【问题讨论】:
C++0x 添加hash<...>(...)。
我找不到hash_combine 函数,如boost 中所示。实现这样的事情的最干净的方法是什么?也许,使用 C++0x xor_combine?
【问题讨论】:
好吧,就像提拔的人那样做:
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);
}
【讨论】:
std::pair(甚至tuple)的哈希值。它会计算每个元素的哈希值,然后将它们组合起来。 (并且本着标准库的精神,以实现定义的方式。)
我将在这里分享它,因为它对寻找此解决方案的其他人有用:从 @KarlvonMoor 答案开始,这是一个可变参数模板版本,如果你必须这样做,它的用法会更简洁将多个值组合在一起:
inline void hash_combine(std::size_t& seed) { }
template <typename T, typename... Rest>
inline void hash_combine(std::size_t& seed, const T& v, Rest... rest) {
std::hash<T> hasher;
seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
hash_combine(seed, rest...);
}
用法:
std::size_t h=0;
hash_combine(h, obj1, obj2, obj3);
这最初是为了实现一个可变参数宏来轻松地使自定义类型可散列(我认为这是hash_combine 函数的主要用途之一):
#define MAKE_HASHABLE(type, ...) \
namespace std {\
template<> struct hash<type> {\
std::size_t operator()(const type &t) const {\
std::size_t ret = 0;\
hash_combine(ret, __VA_ARGS__);\
return ret;\
}\
};\
}
用法:
struct SomeHashKey {
std::string key1;
std::string key2;
bool key3;
};
MAKE_HASHABLE(SomeHashKey, t.key1, t.key2, t.key3)
// now you can use SomeHashKey as key of an std::unordered_map
【讨论】:
几天前我想出了this answer的稍微改进的版本(需要C++ 17支持):
template <typename T, typename... Rest>
void hashCombine(uint& seed, const T& v, Rest... rest)
{
seed ^= ::qHash(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
(hashCombine(seed, rest), ...);
}
上面的代码在代码生成方面更好。我在代码中使用了来自 Qt 的 qHash 函数,但也可以使用任何其他哈希器。
【讨论】:
(int[]){0, (hashCombine(seed, rest), 0)...};,它也适用于C++11。
这也可以通过使用可变参数模板来解决,如下所示:
#include <functional>
template <typename...> struct hash;
template<typename T>
struct hash<T>
: public std::hash<T>
{
using std::hash<T>::hash;
};
template <typename T, typename... Rest>
struct hash<T, Rest...>
{
inline std::size_t operator()(const T& v, const Rest&... rest) {
std::size_t seed = hash<Rest...>{}(rest...);
seed ^= hash<T>{}(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
return seed;
}
};
用法:
#include <string>
int main(int,char**)
{
hash<int, float, double, std::string> hasher;
std::size_t h = hasher(1, 0.2f, 2.0, "Hello World!");
}
当然可以创建一个模板函数,但这可能会导致一些讨厌的类型推断,例如hash("Hallo World!") 将在指针上而不是在字符串上计算哈希值。这可能是标准使用结构的原因。
【讨论】:
answer by vt4a2h 确实不错,但使用 C++17 折叠表达式,并不是每个人都能轻松切换到更新的工具链。下面的版本使用扩展技巧来模拟折叠表达式,并且在 C++11 和 C++14 中也可以使用。
此外,我标记了函数inline,并对可变参数模板参数使用完美转发。
template <typename T, typename... Rest>
inline void hashCombine(std::size_t &seed, T const &v, Rest &&... rest) {
std::hash<T> hasher;
seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
(int[]){0, (hashCombine(seed, std::forward<Rest>(rest)), 0)...};
}
【讨论】:
我真的很喜欢 answer by vt4a2h 中的 C++17 方法,但是它存在一个问题:Rest 是按值传递的,而通过 const 引用传递它们会更可取(即如果它可以与仅移动类型一起使用,则必须这样做)。
这是改编后的版本,它仍然使用fold expression(这就是它需要C++17或更高版本的原因)并使用std::hash(而不是Qt哈希函数):
template <typename T, typename... Rest>
void hash_combine(std::size_t& seed, const T& v, const Rest&... rest)
{
seed ^= std::hash<T>{}(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
(hash_combine(seed, rest), ...);
}
为了完整起见:所有可用于此版本的hash_combine 的类型都必须将template specialization 用于hash 注入std 命名空间。
例子:
namespace std // Inject hash for B into std::
{
template<> struct hash<B>
{
std::size_t operator()(B const& b) const noexcept
{
std::size_t h = 0;
cgb::hash_combine(h, b.firstMember, b.secondMember, b.andSoOn);
return h;
}
};
}
因此,上面示例中的类型 B 也可以在另一个类型 A 中使用,如以下用法示例所示:
struct A
{
std::string mString;
int mInt;
B mB;
B* mPointer;
}
namespace std // Inject hash for A into std::
{
template<> struct hash<A>
{
std::size_t operator()(A const& a) const noexcept
{
std::size_t h = 0;
cgb::hash_combine(h,
a.mString,
a.mInt,
a.mB, // calls the template specialization from above for B
a.mPointer // does not call the template specialization but one for pointers from the standard template library
);
return h;
}
};
}
【讨论】:
Hash 模板参数来指定您的自定义哈希,而不是将其注入std 命名空间。
您可以使用我开发的rst C++ 库来做到这一点:
#include "rst/stl/hash.h"
struct Point {
Point(const int x, const int y) : x(x), y(y) {}
int x = 0;
int y = 0;
};
bool operator==(const Point lhs, const Point rhs) {
return (lhs.x == rhs.x) && (lhs.y == rhs.y);
}
namespace std {
template <>
struct hash<Point> {
size_t operator()(const Point point) const {
return rst::HashCombine({point.x, point.y});
}
};
}
【讨论】:
answer by Henri Menke 效果很好,但如果您将警告视为错误,例如:
add_compile_options(-Werror)
GCC 9.3.0 会给出这个错误:
Test.h:223:67: error: ISO C++ forbids compound-literals [-Werror=pedantic]
223 | (int[]){0, (hashCombine(seed, std::forward<Rest>(rest)), 0)...};
| ^
cc1plus: all warnings being treated as errors
我们可以更新代码来避免这样的错误:
template <typename T, typename... Rest>
inline void hashCombine(std::size_t &seed, T const &v, Rest &&... rest) {
std::hash<T> hasher;
seed ^= (hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2));
int i[] = { 0, (hashCombine(seed, std::forward<Rest>(rest)), 0)... };
(void)(i);
}
【讨论】: