web-dev-qa-db-ja.com

unordered_map / unordered_setのタプルの汎用ハッシュ

なぜ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 {...}  }; 

タプルをキーとして使用した順序付けられていないマップの作成 (Matthieu M.)は、これをboost::Tupleに対して自動化する方法を示しています。可変テンプレートを使用せずにc ++ 0xタプルに対してこれを行う方法はありますか?

確かにこれは標準にあるはずです:(

28
Leo Goodstadt

これはgcc 4.5で機能し、標準のハッシュ可能な型を含むすべてのc ++ 0xタプルをunordered_mapおよびunordered_setのメンバーにすることができます。 (コードをヘッダーファイルに入れてインクルードするだけです。)

関数は、引数に依存する名前のルックアップ(ADL)によって取得されるように、std名前空間に存在する必要があります。

より簡単な解決策はありますか?

#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_Tuplestd::ではなく、独自の名前空間です。

これを行うには、最初に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::hashstd::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;                                 
        }                                              
    };

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

}
9
Вова

C++ 20では、 fold expression および generic lambdas を使用して、再帰なしでタプルのハッシュを計算できます。ハッシュを手動で組み合わせるのではなく、_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));
    }
};
_

sizeof(uintmax_t) * 4 - 1の_- 1_はオプションですが、ハッシュの分布がわずかに改善されているようです。このクラスは、_std::Tuple_および_std::pair_の両方で使用できます。