Как специализировать 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, как для типов, идущих вместе с компилятором и библиотекой. После консультации

кажется возможным специализироваться 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 --- вот мои вопросы:

  1. Законно ли добавлять такую ​​специализацию в пространство имен? std? У меня смешанные чувства по этому поводу.

  2. Какой из std::hash<X>::operator() версии, если таковые имеются, соответствует стандарту C++11?

  3. Есть ли портативный способ сделать это?

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,
Другие вопросы по тегам