Как специализировать 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 
}                                                                             

Предоставление поддержки компилятора для С++ 11 еще экспериментально --- я не пробовал Clang ---, это мои вопросы:

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

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

  • Есть ли способ передвижения?

Ответ 1

Вам явно разрешено и рекомендуется добавлять специализации в пространство имен 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.)

*), если один из задействованных типов определяется пользователем. Я полагаю.

Ответ 2

Моя ставка будет в аргументе шаблона 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>()
    • любое выражение связи, удовлетворяющее сигнатуре -

Ответ 3

@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.