C++ 如何在C++;0x?

C++ 如何在C++;0x?,c++,c++11,boost,hash,std,C++,C++11,Boost,Hash,Std,C++0x添加哈希(…) 但是,我找不到如中所示的hash\u combine函数。实现这样的东西最干净的方法是什么?也许,使用C++0xxor\u combine?好吧,就像boost的家伙那样: template <class T> inline void hash_combine(std::size_t& seed, const T& v) { std::hash<T> hasher; seed ^= hasher(v) + 0x9e

C++0x添加
哈希(…)


但是,我找不到如中所示的
hash\u combine
函数。实现这样的东西最干净的方法是什么?也许,使用C++0x
xor\u combine

好吧,就像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);
}
模板
内联无效哈希组合(标准::大小t&seed,常量t&v)
{
散列哈希器;
seed^=hasher(v)+0x9e3779b9+(seed2);
}

我将在这里分享它,因为它对寻找此解决方案的其他人很有用:从@KarlvonMoor answer开始,这里有一个可变模板版本,如果您必须将多个值组合在一起,它的用法会更简洁:

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...);
}
这本书最初是为了实现一个可变宏,以便轻松地使自定义类型可散列(我认为这是
hash\u combine
函数的主要用途之一):


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

#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;
    }
};
#包括
模板结构散列;
模板
结构散列
:public std::hash
{
使用std::hash::hash;
};
模板
结构散列
{
内联std::size\u t运算符()(常量t&v、常量Rest和…Rest){
std::size\u t seed=hash{}(rest…);
seed^=hash{}(v)+0x9e3779b9+(seed>2);
返回种子;
}
};
用法:

std::size_t h=0;
hash_combine(h, obj1, obj2, obj3);
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
#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!");
}
#包括
int main(int,char**)
{
散列哈希器;
std::size_t h=hasher(1,0.2f,2.0,“你好,世界!”);
}

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

几天前,我提出了稍微改进的版本(需要C++17支持):

模板
无效哈希组合(uint和seed、const T和v、Rest…Rest)
{
seed^=:qHash(v)+0x9e3779b9+(seed>2);
(种子,其余的,…);
}

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

我非常喜欢来自的C++17方法,但是它遇到了一个问题:
Rest
是按值传递的,而更希望按常量引用传递它们(如果只能用于移动类型,则必须使用)

以下是修改后的版本,它仍然使用a(这就是它需要C++17或更高版本的原因),并使用
std::hash
(而不是Qt hash函数):

因此,上述示例中的类型
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;
        }
    };
}
结构A { std::字符串mString; 国际铸币厂; B mB; B*mPointer; } 命名空间std//将的哈希注入std:: { 模板结构哈希 { std::size\u t运算符()(常数&A)常数noexcept { 标准:尺寸h=0; cgb::哈希_组合(h, a、 mString, a、 造币厂, a、 mB,//从上面为B调用模板专门化 a、 mPointer//不调用模板专用化,而是调用标准模板库中指针的专用化 ); 返回h; } }; } 当然不错,但是使用了C++17折叠表达式,并且不是每个人都能够轻松切换到更新的工具链。下面的版本使用扩展技巧来模拟折叠表达式,并在C++11C++14中工作

此外,我还标记了函数
inline
,并对可变模板参数使用完美转发

模板
内联无效哈希组合(标准::大小\u t和种子、t常量和v、剩余和剩余){
散列哈希器;
seed^=hasher(v)+0x9e3779b9+(seed>2);
(int[]){0,(hashCombine(seed,std::forward(rest)),0);
}

该方法非常有效,但如果您将警告视为错误,例如:

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
<代码>测试:H:223:67:错误:ISO C++禁止复合文字(Werror =学究式) 223 |(int[]){0,(hashCombine(seed,std::forward(rest)),0); | ^ cc1plus:所有警告都被视为错误 我们可以更新代码以避免如下错误:

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);
}
模板
内联无效哈希组合(标准::大小\u t和种子、t常量和v、剩余和剩余){
散列哈希器;
seed^=(hasher(v)+0x9e3779b9+(seed>2));
inti[]={0,(hashCombine(seed,std::forward(rest)),0);
(无效)(i);
}

是的,这也是我能做的最好的了。我不明白标准委员会怎么会拒绝这么明显的事情。@Neil:我同意。我认为对他们来说,一个简单的解决方案是要求库为
std::pair
(甚至
tuple
)设置一个哈希值。它会计算每个元素的哈希值,然后将它们组合起来。(根据标准库的精神,以实现定义的方式)标准中有许多明显的遗漏。密集的同行评审过程使得这些小东西很难被公开。为什么这里会有这些神奇的数字?上面的机器不依赖吗(例如,在x86和x64平台上是否会有所不同)?有一篇文章建议包含hash_combine为什么种子总是分别按6和2位移位?将折叠表达式写成
(int[]){0,(hashCombine(seed,rest),0)在我看来,使用标准容器的
Hash
模板参数来指定自定义hasher比将其注入
std
命名空间更好。看起来好多了,谢谢!我可能不在乎通过值传递,因为我使用了一些隐式共享的obj例如,ects与QString类似。您应该只使用edi
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;
        }
    };
}
add_compile_options(-Werror)
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);
}