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

时间:2010-04-07 07:28:38

标签: c++ c++11 boost hash std

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

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

6 个答案:

答案 0 :(得分:79)

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

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

答案 1 :(得分:28)

我会在这里分享它,因为它对寻找这个解决方案的其他人有用:从 @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

答案 2 :(得分:2)

我真的很喜欢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可以使用的所有类型必须将hash的{​​{3}}注入到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;
        }
    };
}

答案 3 :(得分:1)

answer by vt4a2h当然很不错,但是使用C ++ 17 fold表达式,并不是每个人都能轻松切换到较新的工具链。下面的版本使用扩展器技巧来模拟折叠表达式,并且在 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)...};
}

Live example on Compiler Explorer

答案 4 :(得分:0)

几天前,我想出了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函数,但是也可以使用任何其他哈希器。

答案 5 :(得分:0)

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