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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2590677/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 00:01:00  来源:igfitidea点击:

How do I combine hash values in C++0x?

c++c++11boosthashstd

提问by Neil G

C++0x adds hash<...>(...).

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

I could not find a hash_combinefunction though, as presented in boost. What is the cleanest way to implement something like this? Perhaps, using C++0x xor_combine?

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

回答by Karl von Moor

Well, just do it like the boost guys did it:

好吧,就像 boost 人那样做:

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);
}

回答by Matteo Italia

I'll share it here since it can be useful to others looking for this solution: starting from @KarlvonMooranswer, here's a variadic template version, which is terser in its usage if you have to combine several values together:

我将在此处分享它,因为它对寻找此解决方案的其他人很有用:从@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...);
}

Usage:

用法:

std::size_t h=0;
hash_combine(h, obj1, obj2, obj3);

This was written originally to implement a variadic macro to easily make custom types hashable (which I think is one of the primary usages of a hash_combinefunction):

这最初是为了实现一个可变参数宏来轻松地使自定义类型可散列(我认为这是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;\
            }\
        };\
    }

Usage:

用法:

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

回答by kiloalphaindia

This could also be solved by using a variadic template as follows:

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

#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;
    }
};

Usage:

用法:

#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!");
}

One could certainly make a template function, but this could cause some nasty type deduction e.g hash("Hallo World!")will calculate a hash value on the pointer rather than on the string. This is probably the reason, why the standard uses a struct.

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

回答by vt4a2h

A few days ago I came up with slightly improved version of this answer(C++ 17 support is required):

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

The code above is better in terms of code generation. I used qHash function from Qt in my code, but it's also possible to use any other hashers.

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

回答by j00hi

I really like the C++17 approach from the answer by vt4a2h, however it suffers from a problem: The Restis passed on by value whereas it would be more desirable to pass them on by const references (which is a must if it shall be usable with move-only types).

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

Here is the adapted version which still uses a fold expression(which is the reason why it requires C++17 or above) and uses std::hash(instead of the Qt hash function):

这是仍然使用折叠表达式(这就是它需要 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), ...);
}

For completeness sake: All the types which shall be usable with this version of hash_combinemust have a template specializationfor hashinjected into the stdnamespace.

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

Example:

例子:

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;
        }
    };
}

So that type Bin the example above is also usable within another type A, like the following usage example shows:

所以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;
        }
    };
}

回答by Henri Menke

The answer by vt4a2his certainly nice but uses the C++17 fold expression and not everyone is able to switch to a newer toolchain easily. The version below uses the expander trick to emulate a fold expression and works in C++11and C++14as well.

vt4a2h答案当然很好,但使用 C++17 折叠表达式,并不是每个人都能轻松切换到更新的工具链。下面的版本使用扩展器技巧来模拟折叠表达式,并且也适用于C++11C++14

Additionally, I marked the function inlineand use perfect forwarding for the variadic template arguments.

此外,我标记了该函数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)...};
}

Live example on Compiler Explorer

Compiler Explorer 上的实时示例