C ++ 0xはを追加しますhash<...>(...)
。
ブーストでhash_combine
提示されているように、私は関数を見つけることができませんでした。このようなものを実装するための最もクリーンな方法は何ですか?おそらく、C ++ 0xを使用していますか?xor_combine
回答:
まあ、ブーストの人がやったようにそれをやってください:
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);
}
std::pair
(またはtuple
、さえ)。各要素のハッシュを計算し、それらを結合します。(そして、標準ライブラリの精神で、実装で定義された方法で。)
この解決策を探している他の人に役立つ可能性があるため、ここで共有します。@ 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
これは、次のように可変個引数テンプレートを使用することでも解決できます。
#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!")
により、文字列ではなくポインタでハッシュ値を計算するなど、厄介な型の推測が発生する可能性があります。これがおそらく、標準が構造体を使用する理由です。
数日前、私はこの回答のわずかに改善されたバージョンを思いつきました(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関数を使用しましたが、他のハッシャーを使用することもできます。
(int[]){0, (hashCombine(seed, rest), 0)...};
と、C ++ 11でも機能します。
私は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
必要です。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;
}
};
}
Hash
、std
名前空間に挿入するのではなく、標準コンテナーのテンプレート引数を使用してカスタムハッシャーを指定する方が適切です。
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)...};
}