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

问题描述 投票:74回答:4

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

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

c++ c++11 boost hash std
4个回答
81
投票

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

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

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

用法:

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

4
投票

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

#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!")会计算指针上的哈希值而不是字符串。这可能是标准使用结构的原因。


3
投票

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


1
投票

我真的很喜欢来自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;
        }
    };
}
© www.soinside.com 2019 - 2024. All rights reserved.