Para oferecer suporte a tipos de chaves definidas pelo usuário em std::unordered_set<Key>
e std::unordered_map<Key, Value>
é necessário fornecer operator==(Key, Key)
um functor hash:
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;
Seria mais conveniente escrever apenas std::unordered_set<X>
com um hash padrão para o tipo X
, como para os tipos que vêm junto com o compilador e a biblioteca. Depois de consultar
- C ++ Standard Draft N3242 §20.8.12 [unord.hash] e §17.6.3.4 [hash.requirements],
- Boost.Unordered
- g ++
include\c++\4.7.0\bits\functional_hash.h
- VC10
include\xfunctional
- várias questões relacionadas no Stack Overflow
parece possível se especializar 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
}
Dado que o suporte do compilador para C ++ 11 ainda é experimental --- eu não tentei o Clang ---, estas são as minhas perguntas:
É legal adicionar essa especialização ao namespace
std
? Tenho sentimentos mistos sobre isso.Qual das
std::hash<X>::operator()
versões, se houver, é compatível com o padrão C ++ 11?Existe uma maneira portátil de fazer isso?
fonte
operator==(const Key, const Key)
std::hash
(ao contrário de outras coisas nostd
namespace) é desencorajada pelo guia de estilo do Google ; tome-o com um grão de sal.Respostas:
Você está expressamente autorizado e encorajado a adicionar especializações ao namespace
std
*. A maneira correta (e basicamente única) de adicionar uma função hash é esta:(Outras especializações populares que você pode considerar apoiar são
std::less
,std::equal_to
estd::swap
.)*) contanto que um dos tipos envolvidos seja definido pelo usuário, suponho.
fonte
unorder_map<eltype, hash, equality>
vez disso, para evitar estragar o dia de alguém com negócios ADL engraçados. ( Edite o conselho de Pete Becker sobre este tópico )operator==
.) Minha filosofia geral é que se a função é natural e essencialmente a única "correta" (como comparação de pares lexicográficos), então eu a adicionostd
. Se for algo peculiar (como comparação de pares não ordenados), eu o tornarei específico para um tipo de contêiner.Minha aposta seria no argumento do modelo Hash para as classes unordered_map / unorder_set / ...:
Claro
struct Xhasher { size_t operator(const X&) const; };
)std::hash<X>()
fonte
std::hash
ainda seja a melhor saída :-)char*
!hash
especialização interfere via ADL? Quer dizer, é totalmente plausível, mas é difícil encontrar uma pasta de problemas.std::unordered_map<Whatever, Xunset>
e não funciona porque seuXunset
tipo de hasher não pode ser construído por padrão.@Kerrek SB cobriu 1) e 3).
2) Embora g ++ e VC10 declarem
std::hash<T>::operator()
com assinaturas diferentes, ambas as implementações de biblioteca são compatíveis com o padrão.O padrão não especifica os membros de
std::hash<T>
. Diz apenas que cada especialização deve satisfazer os mesmos requisitos de "Hash" necessários para o segundo argumento de modelo destd::unordered_set
e assim por diante. Nomeadamente:H
é um objeto de função, com pelo menos um tipo de argumentoKey
.H
é uma cópia construtível.H
é destrutível.h
for uma expressão do tipoH
ouconst H
, ek
for uma expressão de um tipo conversível em (possivelmenteconst
)Key
, entãoh(k)
é uma expressão válida com tiposize_t
.h
for uma expressão do tipoH
ouconst H
, eu
for um valor l do tipoKey
, entãoh(u)
é uma expressão válida com o tiposize_t
que não se modificau
.fonte
std::hash<X>::operator()
vez destd::hash<X>
como um todo, e a assinatura destd::hash<T>::operator()
é definida pela implementação.