C ++ 0x добавляет hash<...>(...)
.
Однако я не смог найти hash_combine
функцию, представленную в boost . Как проще всего реализовать что-то подобное? Возможно, используя 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
функции):#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), ...); }
Приведенный выше код лучше с точки зрения генерации кода. Я использовал функцию qHash из Qt в своем коде, но также можно использовать любые другие хешеры.
источник
(int[]){0, (hashCombine(seed, rest), 0)...};
и оно также будет работать в C ++ 11.Мне очень нравится подход C ++ 17 из ответа vt4a2h , однако он страдает от проблемы:
Rest
передается по значению, тогда как было бы более желательно передавать их по константным ссылкам (что является обязательным, если оно должно быть можно использовать с типами только для перемещения).Вот адаптированная версия, которая по-прежнему использует выражение свертки (поэтому для нее требуется 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)...}; }
Живой пример в Compiler Explorer
источник