C ++ 0xでハッシュ値を組み合わせるにはどうすればよいですか?


87

C ++ 0xはを追加しますhash<...>(...)

ブーストでhash_combine提示されているように、私は関数を見つけることができませんでした。このようなものを実装するための最もクリーンな方法は何ですか?おそらく、C ++ 0xを使用していますか?xor_combine

回答:


93

まあ、ブーストの人がやったようにそれをやってください:

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

26
ええ、それは私もできる最善のことです。規格委員会がどうしてそんなに明白なことを断ったのか理解できません。
ニールG

13
@ニール:同意します。それらの簡単な解決策は、ライブラリがハッシュを持っている必要があることだと思いますstd::pair(またはtuple、さえ)。各要素のハッシュを計算し、それらを結合します。(そして、標準ライブラリの精神で、実装で定義された方法で。)
GManNickG 2010

3
標準から省略されている明らかなことがたくさんあります。集中的なピアレビューのプロセスは、それらのささいなことを戸外に出すことを困難にします。
stinky472 2010年

15
なぜこれらの魔法数がここにあるのですか?そして、上記はマシンに依存していませんか(たとえば、x86プラットフォームとx64プラットフォームで違いはありません)?
einpoklum 2014年


35

この解決策を探している他の人に役立つ可能性があるため、ここで共有します。@ 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関数の主な使用法の1つだと思います)。

#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

シードが常にそれぞれ6と2だけビットシフトされるのはなぜですか?
j00hi

5

これは、次のように可変個引数テンプレートを使用することでも解決できます。

#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!")により、文字列ではなくポインタでハッシュ値を計算するなど、厄介な型の推測が発生する可能性があります。これがおそらく、標準が構造体を使用する理由です。


4

数日前、私はこの回答のわずかに改善されたバージョンを思いつきました(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関数を使用しましたが、他のハッシャーを使用することもできます。


fold式をとして記述する(int[]){0, (hashCombine(seed, rest), 0)...};と、C ++ 11でも機能します。
アンリ・メンケ

3

私はvt4a2hによる回答からのC ++ 17アプローチが本当に好きですが、問題があります。Rest値によって渡されるのに対し、const参照によって渡される方が望ましいです(これは必須です。移動専用タイプで使用可能)。

これは、まだfold式を使用し(これが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必要です。hashstd

例:

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

私の意見ではHashstd名前空間に挿入するのではなく、標準コンテナーのテンプレート引数を使用してカスタムハッシャーを指定する方が適切です。
アンリ・メンケ

3

vt4a2hによって答えは確かにいいですが、C ++ 17倍式を使用していない、誰もが簡単に、より新しいツールチェーンに切り替えることができます。以下のバージョンは、エキスパンダートリックを使用してフォールド式をエミュレートし、C ++ 11およびC ++ 14でも機能します。

さらに、関数にマークを付けinline、可変個引数テンプレートの引数に完全転送を使用します。

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[]){0, (hashCombine(seed, std::forward<Rest>(rest)), 0)...};
}

コンパイラエクスプローラーの実例


よく見えます、ありがとう!QStringなど、暗黙的に共有されるオブジェクトを使用したため、値の受け渡しについてはおそらく気にしませんでした。
vt4a2h
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.