如何在C++ 0x中组合哈希值?

Nei*_*l G 78 c++ hash boost std c++11

C++ 0x添加hash<...>(...).

我找不到一个hash_combine函数,如boost中所示.实现这样的事最简洁的方法是什么?也许,使用C++ 0x xor_combine

Kar*_*oor 83

好吧,就像升力家伙那样做:

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);
}
Run Code Online (Sandbox Code Playgroud)

  • 是的,那是我能做的最好的.我不明白标准委员会如何拒绝这么明显的事情. (21认同)
  • 为什么这些神奇数字在这?并不是上面的机器依赖(例如,它不会在x86和x64平台上有所不同)? (12认同)
  • @Neil:我同意.我认为对它们来说一个简单的解决方案是要求库具有`std :: pair`(或`tuple`,even)的哈希值.它会计算每个元素的哈希值,然后将它们组合起来.(并且在标准库的精神中,以实现定义的方式.) (10认同)
  • 标准中省略了许多明显的事情.密集的同行评审过程使得很难将这些小事情搞得一团糟. (3认同)
  • 有一篇论文建议包含hash_combine [here](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3333.html) (3认同)
  • 我想一个好的组合方法需要知道如何对各个部分进行哈希处理...某些哈希方法可能会遇到某些组合器的问题.这只是我受过教育的猜测......如果这是真的,很难看出你如何能够以合理的方式标准化这一点. (3认同)
  • @einpoklum:如果不同则没关系.通常,散列函数仅对给定进程是一致的.请参阅SSJ_GZ评论中的链接. (3认同)
  • 在std lib中有这样的东西,还是std lib的即将到来的版本? (2认同)
  • 也许标准委员会拒绝了这一点,因为它实际上不是一种非常好的组合散列的通用方法?也许没有普遍的好方法,所以标准化一种方法会违反标准库的基本政策? (2认同)

Mat*_*lia 31

我将在这里分享它,因为它对寻找这个解决方案的其他人有用:从@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...);
}
Run Code Online (Sandbox Code Playgroud)

用法:

std::size_t h=0;
hash_combine(h, obj1, obj2, obj3);
Run Code Online (Sandbox Code Playgroud)

这最初是为了实现一个可变参数宏来轻松地使自定义类型可以使用(我认为这是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;\
            }\
        };\
    }
Run Code Online (Sandbox Code Playgroud)

用法:

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
Run Code Online (Sandbox Code Playgroud)


j00*_*0hi 10

我真的很喜欢vt4a2h 答案中的 C++17 方法,但是它遇到了一个问题: 是Rest按值传递的,而更理想的是通过 const 引用传递它们(如果要传递的话,这是必须的)可与仅移动类型一起使用)。

这是改编后的版本,它仍然使用折叠表达式(这就是它需要 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), ...);
}
Run Code Online (Sandbox Code Playgroud)

为了完整起见:此版本可用的所有类型都hash_combine必须具有用于注入命名空间的模板专门化hashstd

例子:

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;
        }
    };
}
Run Code Online (Sandbox Code Playgroud)

因此,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;
        }
    };
}
Run Code Online (Sandbox Code Playgroud)


vt4*_*a2h 6

几天前,我想出了这个答案的稍微改进的版本(需要 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), ...);
}
Run Code Online (Sandbox Code Playgroud)

上面的代码在代码生成方面更好。我在我的代码中使用了 Qt 中的 qHash 函数,但也可以使用任何其他哈希器。


Hen*_*nke 6

vt4a2h 的答案当然很好,但使用 C++17 折叠表达式,并不是每个人都能轻松切换到更新的工具链。下面的版本使用扩展器技巧来模拟折叠表达式,并且也适用于C++11C++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)...};
}
Run Code Online (Sandbox Code Playgroud)

编译器资源管理器上的实时示例


kil*_*dia 5

这也可以通过使用可变参数模板来解决,如下所示:

#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;
    }
};
Run Code Online (Sandbox Code Playgroud)

用法:

#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!");
}
Run Code Online (Sandbox Code Playgroud)

人们当然可以制作一个模板函数,但这可能会导致一些讨厌的类型推导,例如hash("Hallo World!")将在指针上而不是在字符串上计算散列值。这可能就是标准使用结构体的原因。