C ++ 0x fügt hinzu hash<...>(...)
.
Ich konnte jedoch keine hash_combine
Funktion finden , wie in Boost dargestellt . Was ist der sauberste Weg, um so etwas zu implementieren? Vielleicht mit C ++ 0x xor_combine
?
C ++ 0x fügt hinzu hash<...>(...)
.
Ich konnte jedoch keine hash_combine
Funktion finden , wie in Boost dargestellt . Was ist der sauberste Weg, um so etwas zu implementieren? Vielleicht mit C ++ 0x xor_combine
?
Antworten:
Nun, mach es einfach so, wie es die Boost-Jungs gemacht haben:
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
(oder tuple
sogar) zu haben. Es würde den Hash jedes Elements berechnen und sie dann kombinieren. (Und im Geiste der Standardbibliothek, in einer implementierungsdefinierten Weise.)
Ich werde es hier teilen, da es für andere nützlich sein kann, die nach dieser Lösung suchen: Ausgehend von der Antwort von @KarlvonMoor ist hier eine variable Vorlagenversion, die in ihrer Verwendung kürzer ist, wenn Sie mehrere Werte miteinander kombinieren müssen:
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...);
}
Verwendung:
std::size_t h=0;
hash_combine(h, obj1, obj2, obj3);
Dies wurde ursprünglich geschrieben, um ein variadisches Makro zu implementieren, um benutzerdefinierte Typen einfach hashbar zu machen (was meiner Meinung nach eine der Hauptverwendungen einer hash_combine
Funktion ist):
#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;\
}\
};\
}
Verwendung:
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
Dies könnte auch durch Verwendung einer variablen Vorlage wie folgt gelöst werden:
#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;
}
};
Verwendung:
#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!");
}
Man könnte sicherlich eine Vorlagenfunktion erstellen, aber dies könnte zu einem bösen Typabzug führen, z. B. hash("Hallo World!")
wird ein Hash-Wert eher auf dem Zeiger als auf der Zeichenfolge berechnet. Dies ist wahrscheinlich der Grund, warum der Standard eine Struktur verwendet.
Vor einigen Tagen habe ich eine leicht verbesserte Version dieser Antwort gefunden (C ++ 17-Unterstützung ist erforderlich):
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), ...);
}
Der obige Code ist in Bezug auf die Codegenerierung besser. Ich habe die qHash-Funktion von Qt in meinem Code verwendet, aber es ist auch möglich, andere Hascher zu verwenden.
(int[]){0, (hashCombine(seed, rest), 0)...};
und er funktioniert auch in C ++ 11.
Ich mag den C ++ 17-Ansatz aus der Antwort von vt4a2h sehr , aber er hat ein Problem: Der Rest
wird als Wert weitergegeben, während es wünschenswerter wäre, sie durch konstante Referenzen weiterzugeben (was ein Muss ist, wenn es sein soll verwendbar mit Nur-Verschieben-Typen).
Hier ist die angepasste Version, die immer noch einen Fold-Ausdruck verwendet (weshalb C ++ 17 oder höher erforderlich ist) und verwendet std::hash
(anstelle der Qt-Hash-Funktion):
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), ...);
}
Der Vollständigkeit halber: Alle Typen , die mit dieser Version von benutzbar sein soll hash_combine
muss eine haben Template - Spezialisierung für hash
injiziert in den std
Namespace.
Beispiel:
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;
}
};
}
Dieser Typ B
im obigen Beispiel kann also auch in einem anderen Typ verwendet werden A
, wie das folgende Verwendungsbeispiel zeigt:
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
Vorlagenargumente der Standardcontainer zu verwenden, um Ihren benutzerdefinierten Hasher anzugeben, als ihn in den std
Namespace einzufügen .
Die Antwort von vt4a2h ist sicherlich nett, verwendet jedoch den C ++ 17-Fold-Ausdruck und nicht jeder kann problemlos zu einer neueren Toolchain wechseln. Die folgende Version verwendet den Expander-Trick, um einen Fold-Ausdruck zu emulieren, und funktioniert auch in C ++ 11 und C ++ 14 .
Zusätzlich habe ich die Funktion markiert inline
und die perfekte Weiterleitung für die verschiedenen Vorlagenargumente verwendet.
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)...};
}