Как специализировать std:: hash<Key>:: operator () для пользовательского типа в неупорядоченных контейнерах?
Для поддержки пользовательских типов ключей в std::unordered_set<Key>
а также std::unordered_map<Key, Value>
нужно предоставить operator==(Key, Key)
и хеш-функтор:
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;
Было бы удобнее просто написать std::unordered_set<X>
с хешем по умолчанию для типа X
, как для типов, идущих вместе с компилятором и библиотекой. После консультации
- Проект стандарта C++ N3242 § 20.8.12 [unord.hash] и §17.6.3.4 [hash.requirements],
- Boost.Unordered
- г ++
include\c++\4.7.0\bits\functional_hash.h
- VC10
include\xfunctional
- различные связанные вопросы в переполнении стека
кажется возможным специализироваться 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
}
Учитывая, что поддержка компилятора для C++11 пока еще экспериментальна --- я не пробовал Clang --- вот мои вопросы:
Законно ли добавлять такую специализацию в пространство имен?
std
? У меня смешанные чувства по этому поводу.Какой из
std::hash<X>::operator()
версии, если таковые имеются, соответствует стандарту C++11?Есть ли портативный способ сделать это?
3 ответа
Вам прямо разрешено и рекомендуется добавлять специализации в пространство имен std
*. Правильный (и в основном единственный) способ добавить хеш-функцию:
namespace std {
template <> struct hash<Foo>
{
size_t operator()(const Foo & x) const
{
/* your code here, e.g. "return hash<int>()(x.value);" */
}
};
}
(Другие популярные специализации, которые вы могли бы поддержать std::less
, std::equal_to
а также std::swap
.)
*) Полагаю, пока один из задействованных типов определяется пользователем.
Моя ставка будет на аргумент шаблона Hash для классов 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
}
Конечно
- hashX также может быть глобальной статической функцией
- во втором случае вы могли бы передать это
- старомодный объект функтора (
struct Xhasher { size_t operator(const X&) const; };
) std::hash<X>()
- любое связующее выражение, удовлетворяющее подписи -
- старомодный объект функтора (
@Kerrek SB покрыл 1) и 3).
2) Хотя g++ и VC10 объявляют std::hash<T>::operator()
с разными сигнатурами обе реализации библиотеки соответствуют стандарту.
Стандарт не определяет членов std::hash<T>
, Это просто говорит о том, что каждая такая специализация должна удовлетворять тем же требованиям "хеша", которые необходимы для второго аргумента шаблона std::unordered_set
и так далее. А именно:
- Тип хеша
H
является функциональным объектом, по крайней мере с одним типом аргументаKey
, H
является копируемымH
разрушаемо- Если
h
это выражение типаH
или жеconst H
, а такжеk
является выражением типа, преобразуемого в (возможно,const
)Key
, затемh(k)
допустимое выражение с типомsize_t
, - Если
h
это выражение типаH
или жеconst H
, а такжеu
является lvalue типаKey
, затемh(u)
допустимое выражение с типомsize_t
который не меняетu
,