unordered_map/unordered_set中元组的通用哈希

Leo*_*adt 27 c++ unordered-map tuples unordered-set c++11

为什么不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 {...}  }; 
Run Code Online (Sandbox Code Playgroud)

构建一个以元组为键的无序映射(Matthieu M.)展示了如何自动执行此操作boost::tuple.有没有为c ++ 0x元组执行此操作而不使用可变参数模板?

当然这应该在标准:(

Leo*_*adt 22

这适用于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;                                 
        }                                              

    };
}
Run Code Online (Sandbox Code Playgroud)

标准符合代码

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

unordered_set<tuple<double, int> > test_set;
Run Code Online (Sandbox Code Playgroud)

你需要:

unordered_set<tuple<double, int>, hash_tuple::hash<tuple<double, int>>> test2;
Run Code Online (Sandbox Code Playgroud)

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);                                 
    }                                              
};
}
Run Code Online (Sandbox Code Playgroud)

确保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);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后包括所有其他以前的代码,但把它放在里面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;                                 
        }                                              
    };

}
Run Code Online (Sandbox Code Playgroud)

  • @allyourcode这是旧的,但实际上建议在std命名空间中添加特化.明确禁止添加类,函数或其他定义.http://en.cppreference.com/w/cpp/language/extending_std (4认同)
  • 不值得未定义的行为:不要专门处理`std ::`中涉及你不拥有的东西,而你没有`std :: tuple <TT ...>`.作为一个如何可能破坏代码的具体例子,当标准的新迭代引入其自己的哈希特化时会发生什么?如果有其他有明智想法的人引入了一个狭窄的`hash <tuple <int >>`specialization,从一些但不是所有使用`hash <tuple <int >>`的地方都可以看到会发生什么?这些都是具体的例子,但UB并没有受到它们的限制.你的程序形成不良. (3认同)
  • 有std :: hash_combine吗? (2认同)
  • @AlexanderHuszagh 您只能为自定义类型将特化添加到 `std` 命名空间中(因此不适用于 `std::tuple`)。这就是 Yakk 在他/她的评论中提到的。 (2认同)

Вов*_*ова 9

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

}
Run Code Online (Sandbox Code Playgroud)


Moo*_*uck 7

在我的C++ 0x草案中,20.8.15hash表示专门用于内置类型(包括指针,但似乎并不暗示取消引用它们).这似乎也可以专门用于error_code,bitset<N>,unique_ptr<T, D>,shared_ptr<T>,typeindex,string,u16string,u32string,wstring,vector<bool, Allocator>,和thread::id.(表示清单!)

我没有使用过C++ 0x variadics,所以我的格式化可能很偏僻,但这些行中的某些内容可能适用于所有元组.

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

这个版本实际上编译并运行

Yakk观察到技术上不允许std::hash直接进行专门化,因为我们专注于标准库模板,其声明依赖于用户定义的类型.

  • 必须是一个疏忽,元组不可清洗.虽然标准太晚了:( (3认同)
  • @AlexandreC:`^`和`+`都是可交换的,因此是组合哈希的不良选择.考虑`std :: unordered_set <std :: tuple <int,int,... >>`将如何处理{1,2,...,10}的排列.相反,使用非交换组合器,例如`m*left + right`,其中*m*是一个大的奇数. (3认同)

Vla*_*kov 6

使用 C++20,可以使用折叠表达式泛型 lambda来计算元组的哈希值而无需递归。我更喜欢依赖std::hash<uintmax_t>而不是手动组合哈希:

#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));
    }
};
Run Code Online (Sandbox Code Playgroud)

- 1insizeof(uintmax_t) * 4 - 1是可选的,但似乎稍微改善了哈希分布。此类可以与std::tuple和一起使用std::pair