Як поєднати хеш-значення в C ++ 0x?


87

C ++ 0x додає hash<...>(...).

Я не зміг знайти hash_combineфункцію, як показано у boost . Який найчистіший спосіб реалізувати щось подібне? Можливо, за допомогою C ++ 0x xor_combine?

Відповіді:


93

Ну, просто зробіть це так, як це зробили хлопці:

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

26
так, це найкраще, що я міг зробити теж. Я не розумію, як комітет стандартизації відхилив щось настільки очевидне.
Ніл Г

13
@ Ніл: Я згоден. Я думаю, що простим рішенням для них буде вимога бібліотеки мати хеш для std::pair(або tupleнавіть). Він обчислює хеш кожного елемента, а потім комбінує їх. (І в дусі стандартної бібліотеки, у визначенні реалізації.)
GManNickG

3
Є багато очевидних речей, опущених із стандарту. Процес інтенсивного експертного оцінювання ускладнює виведення цих дрібниць із дверей.
stinky472

15
Навіщо ці магічні цифри тут? І чи не вищезазначене залежить від машини (наприклад, чи не буде воно відрізнятися на платформах x86 та x64)?
einpoklum

3
Є стаття, що пропонує включити сюди
hash_combine

35

Я поділюсь ним тут, оскільки це може бути корисним для інших, хто шукає це рішення: починаючи з відповіді @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

Чому насіння завжди потроху зміщується на 6 та 2 відповідно?
j00hi

5

Це також можна вирішити, використовуючи варіативний шаблон наступним чином:

#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!")буде обчислювати хеш-значення на покажчику, а не на рядку. Це, мабуть, причина, чому стандарт використовує struct.


4

Кілька днів тому я придумав трохи вдосконалену версію цієї відповіді (потрібна підтримка 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 у своєму коді, але також можна використовувати будь-які інші хеш-машини.


Напишіть вираз fold як, (int[]){0, (hashCombine(seed, rest), 0)...};і він також буде працювати в C ++ 11.
Анрі Менке,

3

Мені дуже подобається підхід C ++ 17 із відповіді vt4a2h , однак він страждає від проблеми: The 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простір імен.
Анрі Менке,

3

Відповідь на 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


Виглядає набагато краще, дякую! Мені, мабуть, було байдуже про передачу за значенням, оскільки я використовував деякі неявно спільні об'єкти, наприклад, наприклад, QString.
vt4a2h
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.