C ++ 0x aggiunge hash<...>(...)
.
hash_combine
Tuttavia, non sono riuscito a trovare una funzione, come presentato in boost . Qual è il modo più pulito per implementare qualcosa di simile? Forse, usando C ++ 0x xor_combine
?
Risposte:
Bene, fallo come hanno fatto i ragazzi del boost:
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
(o tuple
, anche). Calcolerebbe l'hash di ogni elemento, quindi li combina. (E nello spirito della libreria standard, in un modo definito di implementazione.)
Lo condividerò qui poiché può essere utile ad altri che cercano questa soluzione: partendo dalla risposta di @KarlvonMoor , ecco una versione del modello variadico, che è più tesa nel suo utilizzo se devi combinare più valori insieme:
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...);
}
Utilizzo:
std::size_t h=0;
hash_combine(h, obj1, obj2, obj3);
Questo è stato scritto originariamente per implementare una macro variadica per rendere facilmente hashable i tipi personalizzati (che penso sia uno degli usi primari di una hash_combine
funzione):
#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;\
}\
};\
}
Utilizzo:
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
Questo potrebbe anche essere risolto utilizzando un modello variadico come segue:
#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;
}
};
Utilizzo:
#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!");
}
Si potrebbe certamente creare una funzione modello, ma ciò potrebbe causare una sgradevole deduzione di tipo, ad esempio hash("Hallo World!")
calcolerà un valore hash sul puntatore piuttosto che sulla stringa. Questo è probabilmente il motivo per cui lo standard utilizza una struttura.
Qualche giorno fa ho trovato una versione leggermente migliorata di questa risposta (è richiesto il supporto 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), ...);
}
Il codice sopra è migliore in termini di generazione del codice. Ho usato la funzione qHash di Qt nel mio codice, ma è anche possibile utilizzare qualsiasi altro hash.
(int[]){0, (hashCombine(seed, rest), 0)...};
e funzionerà anche in C ++ 11.
Mi piace molto l'approccio C ++ 17 dalla risposta di vt4a2h , tuttavia soffre di un problema: Rest
viene trasmesso per valore mentre sarebbe più desiderabile trasmetterli tramite riferimenti const (che è un must se deve essere utilizzabile con i tipi di solo movimento).
Ecco la versione adattata che utilizza ancora un'espressione di piegatura (che è il motivo per cui richiede C ++ 17 o superiore) e utilizza std::hash
(invece della funzione 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), ...);
}
Per completezza: tutti i tipi che devono essere utilizzabili con questa versione di hash_combine
devono avere una specializzazione di template da hash
inserire nello std
spazio dei nomi.
Esempio:
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;
}
};
}
Quindi quel tipo B
nell'esempio sopra è utilizzabile anche all'interno di un altro tipo A
, come mostra il seguente esempio di utilizzo:
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
argomenti del template dei contenitori standard per specificare il tuo hasher personalizzato invece di iniettarlo nello std
spazio dei nomi.
La risposta di vt4a2h è sicuramente carina ma utilizza l'espressione fold C ++ 17 e non tutti sono in grado di passare facilmente a una toolchain più recente. La versione seguente utilizza il trucco dell'espansore per emulare un'espressione di piegatura e funziona anche in C ++ 11 e C ++ 14 .
Inoltre, ho contrassegnato la funzione inline
e utilizzato l'inoltro perfetto per gli argomenti del modello variadic.
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)...};
}