【问题标题】:Generic hash for tuples in unordered_map / unordered_setunordered_map / unordered_set 中元组的通用哈希
【发布时间】:2011-10-29 22:22:23
【问题描述】:

为什么std::unordered_map<tuple<int, int>, string> 不只是 开箱即用? 必须为tuple<int, int> 定义一个哈希函数是很乏味的,例如

template<> struct do_hash<tuple<int, int>>                               
{   size_t operator()(std::tuple<int, int> const& tt) const {...}  }; 

Building an unordered map with tuples as keys (Matthieu M.) 展示了如何 为boost::tuple 自动执行此操作。有没有在不使用可变参数模板的情况下对 c++0x 元组执行此操作?

这当然应该在标准中:(

【问题讨论】:

  • 如果你使用 C++0x 为什么不使用可变参数模板呢? (或者是否有元组的实现,但没有可变模板?)
  • @RMartinho : VC++ 2010 符合该描述(而且似乎下一版本的 VC++ 也可能如此)。
  • 如果 std::tuple 是通过手动写出 1 到 N 元的模板来实现的(如 boost::tuple),那么我猜散列元组所需的 std::hash 特化也将必须以相同的方式手动写出(使用过于聪明的预处理?)。啊!

标签: c++ c++11 tuples unordered-map unordered-set


【解决方案1】:

这适用于 gcc 4.5,允许所有包含标准哈希类型的 c++0x 元组成为 unordered_mapunordered_set 不用多说。 (我将代码放在头文件中并包含它。)

函数必须存在于 std 命名空间中,以便被 参数相关名称查找 (ADL)。

有没有更简单的解决方案?

#include <tuple>
namespace std{
    namespace
    {

        // Code from boost
        // Reciprocal of the golden ratio helps spread entropy
        //     and handles duplicates.
        // See Mike Seymour in magic-numbers-in-boosthash-combine:
        //     http://stackoverflow.com/questions/4948780

        template <class T>
        inline void hash_combine(std::size_t& seed, T const& v)
        {
            seed ^= std::hash<T>()(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
        }

        // Recursive template code derived from Matthieu M.
        template <class Tuple, size_t Index = std::tuple_size<Tuple>::value - 1>
        struct HashValueImpl
        {
          static void apply(size_t& seed, Tuple const& tuple)
          {
            HashValueImpl<Tuple, Index-1>::apply(seed, tuple);
            hash_combine(seed, std::get<Index>(tuple));
          }
        };

        template <class Tuple>
        struct HashValueImpl<Tuple,0>
        {
          static void apply(size_t& seed, Tuple const& tuple)
          {
            hash_combine(seed, std::get<0>(tuple));
          }
        };
    }

    template <typename ... TT>
    struct hash<std::tuple<TT...>> 
    {
        size_t
        operator()(std::tuple<TT...> const& tt) const
        {                                              
            size_t seed = 0;                             
            HashValueImpl<std::tuple<TT...> >::apply(seed, tt);    
            return seed;                                 
        }                                              

    };
}

标准符合代码

Yakk 指出,在 std 命名空间中专门化事物实际上是未定义的行为。如果您希望有一个符合标准的解决方案,那么您需要将所有这些代码移动到您自己的命名空间中,并放弃任何 ADL 自动找到正确哈希实现的想法。而不是:

unordered_set<tuple<double, int> > test_set;

你需要:

unordered_set<tuple<double, int>, hash_tuple::hash<tuple<double, int>>> test2;

其中hash_tuple 是您自己的命名空间,而不是std::

为此,您首先必须在hash_tuple 命名空间内声明一个哈希实现。这会将所有非元组类型转发到std::hash

namespace hash_tuple{

template <typename TT>
struct hash
{
    size_t
    operator()(TT const& tt) const
    {                                              
        return std::hash<TT>()(tt);                                 
    }                                              
};
}

确保hash_combine 调用hash_tuple::hash 而不是std::hash

namespace hash_tuple{

namespace
    {
    template <class T>
    inline void hash_combine(std::size_t& seed, T const& v)
    {
        seed ^= hash_tuple::hash<T>()(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
    }
}

然后包含所有其他以前的代码,但将其放入 namespace hash_tuple 而不是 std::

namespace hash_tuple{

    namespace
    {
        // Recursive template code derived from Matthieu M.
        template <class Tuple, size_t Index = std::tuple_size<Tuple>::value - 1>
        struct HashValueImpl
        {
          static void apply(size_t& seed, Tuple const& tuple)
          {
            HashValueImpl<Tuple, Index-1>::apply(seed, tuple);
            hash_combine(seed, std::get<Index>(tuple));
          }
        };

        template <class Tuple>
        struct HashValueImpl<Tuple,0>
        {
          static void apply(size_t& seed, Tuple const& tuple)
          {
            hash_combine(seed, std::get<0>(tuple));
          }
        };
    }

    template <typename ... TT>
    struct hash<std::tuple<TT...>> 
    {
        size_t
        operator()(std::tuple<TT...> const& tt) const
        {                                              
            size_t seed = 0;                             
            HashValueImpl<std::tuple<TT...> >::apply(seed, tt);    
            return seed;                                 
        }                                              
    };

}

【讨论】:

  • 有 std::hash_combine 吗?
  • 您的模板使用 std::hash_combine 和 gcc 4.6.3 编译。我进行了切换以避免使用 boost
  • 不值得未定义的行为:不要专门化 std:: 中涉及您不拥有的事物的事物,并且您不拥有 std::tuple&lt;TT...&gt;。作为这如何可怕地破坏代码的具体示例,当标准的新迭代引入其自己的哈希专业化时会发生什么?当其他有你好主意的人介绍一个狭窄的hash&lt;tuple&lt;int&gt;&gt; 专业化时会发生什么,这在使用hash&lt;tuple&lt;int&gt;&gt; 的一些但不是所有地方都可见?这些是具体的例子,但 UB 不受它们的限制。您的程序格式不正确。
  • @allyourcode 这是旧的,但实际上建议向 std 命名空间添加特化。明确禁止添加类、函数或其他定义。 en.cppreference.com/w/cpp/language/extending_std
  • @AlexanderHuszagh 您可以将特化添加到 std 命名空间中,仅用于自定义类型(因此不适用于 std::tuple)。这就是 Yakk 在他/她的评论中提到的。
【解决方案2】:
#include <boost/functional/hash.hpp>
#include <tuple>

namespace std
{

template<typename... T>
struct hash<tuple<T...>>
{
    size_t operator()(tuple<T...> const& arg) const noexcept
    {
        return boost::hash_value(arg);
    }
};

}
【解决方案3】:

在我的 C++0x 草案中,20.8.15 说 hash 专门用于内置类型(包括指针,但似乎并不意味着取消引用它们)。它似乎还专门用于error_codebitset&lt;N&gt;unique_ptr&lt;T, D&gt;shared_ptr&lt;T&gt;typeindexstringu16stringu32stringwstringthread::id4 和@。 (有趣的列表!)

我没有使用 C++0x 可变参数,所以我的格式可能有问题,但是这些方面的内容可能适用于所有元组。

size_t hash_combiner(size_t left, size_t right) //replacable
{ return left + 0x9e3779b9 + (right<<6) + (right>>2);}

template<int index, class...types>
struct hash_impl {
    size_t operator()(size_t a, const std::tuple<types...>& t) const {
        typedef typename std::tuple_element<index, std::tuple<types...>>::type nexttype;
        hash_impl<index-1, types...> next;
        size_t b = std::hash<nexttype>()(std::get<index>(t));
        return next(hash_combiner(a, b), t); 
    }
};
template<class...types>
struct hash_impl<0, types...> {
    size_t operator()(size_t a, const std::tuple<types...>& t) const {
        typedef typename std::tuple_element<0, std::tuple<types...>>::type nexttype;
        size_t b = std::hash<nexttype>()(std::get<0>(t));
        return hash_combiner(a, b); 
    }
};

template<class...types>
struct tuple_hash<std::tuple<types...>> {
    size_t operator()(const std::tuple<types...>& t) {
        const size_t begin = std::tuple_size<std::tuple<types...>>::value-1;
        return hash_impl<begin, types...>()(0, t);
    }
}

This version actually compiles and runs

Yakk 发现直接专门化std::hash技术上 不允许的,因为我们正在专门化一个标准库模板,其声明 依赖于用户- 定义类型。

【讨论】:

  • 如果您不知道该怎么做,请使用left() ^ right()。见stackoverflow.com/questions/5889238/…。但请注意,XOR 并不总是正确的选择。如果您希望元组包含重复的成员,则使用普通加法会更好。这可能是元组没有标准哈希的原因。
  • 元组不可散列一定是一个疏忽。虽然标准为时已晚:(
  • @Leo 奇怪的是,除了 vector 没有容器,并且 basic_string 是可散列的。 (bitset 在技术上是一个容器吗?)
  • 哦,我刚刚注意到:without using variadic templates? 哎呀。答案是否定的,不能对所有元组都这样做,因为元组是可变参数模板类型。
  • @AlexandreC.: ^+ 都是可交换的,因此对于组合散列来说是糟糕的选择。考虑std::unordered_set&lt;std::tuple&lt;int, int, …&gt;&gt; 将如何处理 {1, 2, ..., 10} 的排列。相反,请使用非交换组合器,例如 m * left + right,其中 m 是一个很大的奇数。
【解决方案4】:

在 C++20 中,可以使用 fold expressionsgeneric lambdas 来计算元组的哈希值而无需递归。我更喜欢依赖std::hash&lt;uintmax_t&gt; 而不是手动组合哈希:

#include <cinttypes>
#include <cstddef>
#include <functional>
#include <tuple>

class hash_tuple {
    template<class T>
    struct component {
        const T& value;
        component(const T& value) : value(value) {}
        uintmax_t operator,(uintmax_t n) const {
            n ^= std::hash<T>()(value);
            n ^= n << (sizeof(uintmax_t) * 4 - 1);
            return n ^ std::hash<uintmax_t>()(n);
        }
    };

public:
    template<class Tuple>
    size_t operator()(const Tuple& tuple) const {
        return std::hash<uintmax_t>()(
            std::apply([](const auto& ... xs) { return (component(xs), ..., 0); }, tuple));
    }
};

sizeof(uintmax_t) * 4 - 1 中的- 1 是可选的,但似乎略微改善了哈希分布。此类可与std::tuplestd::pair 一起使用。

【讨论】:

  • n ^= n
猜你喜欢
  • 2013-12-11
  • 2020-08-27
  • 2015-09-14
  • 2011-04-17
  • 2011-11-05
  • 1970-01-01
  • 1970-01-01
  • 2015-09-10
  • 1970-01-01
相关资源
最近更新 更多