Comment spécialiser std :: hash <Key> :: operator () pour un type défini par l'utilisateur dans des conteneurs non ordonnés?
Pour prendre en charge les types de clé définis par l'utilisateur dans std::unordered_set<Key>
Et std::unordered_map<Key, Value>
, Il faut fournir operator==(Key, Key)
et un foncteur de hachage:
struct X { int id; /* ... */ };
bool operator==(X a, X b) { return a.id == b.id; }
struct MyHash {
size_t operator()(const X& x) const { return std::hash<int>()(x.id); }
};
std::unordered_set<X, MyHash> s;
Il serait plus pratique d'écrire simplement std::unordered_set<X>
Avec un hachage par défaut pour le type X
, comme pour les types à venir avec le compilateur et la bibliothèque. Après consultation
- Norme C++ Projet N3242 §20.8.12 [unord.hash] et §17.6.3.4 [exigences de hachage],
- Boost.Unordered
- g ++
include\c++\4.7.0\bits\functional_hash.h
- VC10
include\xfunctional
- divers question connexe s dans Stack Overflow
il semble possible de spécialiser std::hash<X>::operator()
:
namespace std { // argh!
template <>
inline size_t
hash<X>::operator()(const X& x) const { return hash<int>()(x.id); } // works for MS VC10, but not for g++
// or
// hash<X>::operator()(X x) const { return hash<int>()(x.id); } // works for g++ 4.7, but not for VC10
}
Étant donné que le support du compilateur pour C++ 11 est encore expérimental - je n’ai pas essayé Clang ---, voici mes questions:
Est-il légal d'ajouter une telle spécialisation à l'espace de noms
std
? J'ai des sentiments mitigés sur ce sujet.Laquelle des versions de
std::hash<X>::operator()
, le cas échéant, est conforme à la norme C++ 11?Y a-t-il un moyen portable de le faire?
Vous êtes expressément autorisé et encouragé à ajouter spécialisations à l'espace de noms std
*. La manière correcte (et fondamentalement unique) d'ajouter une fonction de hachage est la suivante:
namespace std {
template <> struct hash<Foo>
{
size_t operator()(const Foo & x) const
{
/* your code here, e.g. "return hash<int>()(x.value);" */
}
};
}
(Les autres spécialisations populaires que vous pourriez envisager de soutenir sont std::less
, std::equal_to
et std::swap
.)
*) tant que l'un des types impliqués est défini par l'utilisateur, je suppose.
Mon pari serait sur l'argument de modèle de hachage pour les classes unordered_map/unorder_set/...:
#include <unordered_set>
#include <functional>
struct X
{
int x, y;
std::size_t gethash() const { return (x*39)^y; }
};
typedef std::unordered_set<X, std::size_t(*)(const X&)> Xunset;
typedef std::unordered_set<X, std::function<std::size_t(const X&)> > Xunset2;
int main()
{
auto hashX = [](const X&x) { return x.gethash(); };
Xunset my_set (0, hashX);
Xunset2 my_set2(0, hashX); // if you prefer a more flexible set typedef
}
Bien sûr
- hashX pourrait tout aussi bien être une fonction statique globale
- dans le second cas, vous pourriez passer que
- l'objet foncteur à l'ancienne (
struct Xhasher { size_t operator(const X&) const; };
) std::hash<X>()
- toute expression de lien satisfaisant la signature -
- l'objet foncteur à l'ancienne (
@Kerrek SB a couvert 1) et 3).
2) Même si g ++ et VC10 déclarent std::hash<T>::operator()
avec des signatures différentes, les deux implémentations de bibliothèque sont conformes à la norme.
La norme ne spécifie pas les membres de std::hash<T>
. Cela indique simplement que chaque spécialisation de ce type doit satisfaire aux mêmes exigences de "hachage" que celles requises pour le second argument de modèle de std::unordered_set
, Etc. À savoir:
- Le type de hachage
H
est un objet fonction, avec au moins un type d'argumentKey
. H
est une copie constructible.H
est destructible.- Si
h
est une expression de typeH
ouconst H
, Etk
est une expression d'un type convertible en (éventuellementconst
)Key
, alorsh(k)
est une expression valide de typesize_t
. - Si
h
est une expression de typeH
ouconst H
Et queu
est une lvalue de typeKey
, alorsh(u)
est une expression valide de typesize_t
qui ne modifie pasu
.