web-dev-qa-db-ja.com

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

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

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

76
Neil G

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

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);
}
80
Karl von Moor

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

使用法:

std::size_t h=0;
hash_combine(h, obj1, obj2, obj3);

これはもともと、可変型マクロを実装してカスタム型を簡単にハッシュ可能にするために書かれました(これは、hash_combine 関数):

#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
30
Matteo Italia

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

_#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
kiloalphaindia

数日前に この回答 (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関数を使用しましたが、他のハッシング機能を使用することもできます。

3
vt4a2h

vt4a2hによる回答 のC++ 17アプローチが本当に好きですが、問題があります:Restは値で渡されるのに対し、それらを渡す方が望ましいconst参照による(移動のみの型で使用できるようにする場合は必須です)。

fold expression (C++ 17以降が必要な理由です)を使用し、(Qtハッシュ関数の代わりに)std::hashを使用する適応バージョンは次のとおりです。

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で使用できるすべてのタイプには、hash名前空間に挿入されるstdの-​​ テンプレート特殊化 が必要です。

例:

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