web-dev-qa-db-fra.com

Comment combiner des valeurs de hachage en C++ 0x?

C++ 0x ajoute hash<...>(...).

Cependant, je n’ai pas trouvé de fonction hash_combine, comme présenté dans boost . Quel est le moyen le plus propre d'implémenter quelque chose comme ça? Peut-être, en utilisant C++ 0x xor_combine?

70
Neil G

Eh bien, faites-le comme les gars de boost l'ont fait:

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);
}
77
Karl von Moor

Je vais le partager ici car cela peut être utile aux autres personnes qui recherchent cette solution: à partir de @KarlvonMoor answer, voici une version de modèle variadique, dont l'utilisation est floue si vous devez combiner plusieurs valeurs:

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

Usage:

std::size_t h=0;
hash_combine(h, obj1, obj2, obj3);

Cela a été écrit à l'origine pour implémenter une macro variadique afin de rendre facilement les types personnalisés hashable (ce qui est, selon moi, l'une des principales utilisations d'une fonction 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;\
            }\
        };\
    }

Usage:

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
25
Matteo Italia

Cela pourrait également être résolu en utilisant un modèle variadique comme suit:

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

Usage:

#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!");
}

On pourrait certainement créer une fonction de modèle, mais cela pourrait entraîner une déduction de type désagréable, par exemple hash("Hallo World!") calculera une valeur de hachage sur le pointeur plutôt que sur la chaîne. C'est probablement la raison pour laquelle la norme utilise une structure.

3
kiloalphaindia

J'aime beaucoup l'approche C++ 17 de réponse de vt4a2h , mais elle souffre toutefois d'un problème: la Rest est transmise par la valeur alors qu'il serait plus souhaitable de la transmettre. avec des références const (ce qui est indispensable si elle doit être utilisée avec des types uniquement mobiles).

Voici la version adaptée qui utilise toujours une expression expression de pliure (ce qui explique pourquoi elle nécessite C++ 17 ou supérieur) et utilise std::hash (au lieu de la fonction de hachage 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), ...);
}

Par souci d’exhaustivité: Tous les types utilisables avec cette version de hash_combine doivent obligatoirement avoir un spécialisation de modèle pour hash injecté dans l’espace de nom std.

Exemple:

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

Ainsi, le type B de l'exemple ci-dessus est également utilisable dans un autre type A, comme le montre l'exemple d'utilisation suivant:

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;
        }
    };
}
1
j00hi

Il y a quelques jours, j'ai proposé une version légèrement améliorée de cette réponse (le support de C++ 17 est requis):

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

Le code ci-dessus est meilleur en termes de génération de code. J'ai utilisé la fonction qHash de Qt dans mon code, mais il est également possible d'utiliser n'importe quel autre hash.

0
vt4a2h