Як спеціалізувати std :: hash <Key> :: operator () для визначеного користувачем типу в не упорядкованих контейнерах?


101

Для підтримки ключових типів , визначених користувачем в 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. Чи є портативний спосіб це зробити?


З gcc 4.7.2 мені довелося надати глобальнуoperator==(const Key, const Key)
Віктор Любославський

Зауважте, що спеціалізація std::hash(на відміну від інших речей у stdпросторі імен) не перешкоджає посібнику зі стилів Google ; візьміть його з зерном солі.
Франклін Ю

Відповіді:


128

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

*) якщо один із залучених типів визначений користувачем, я думаю.


3
хоча це можливо, ви б взагалі рекомендували робити так? Я б віддав перевагу unorder_map<eltype, hash, equality>замість цього, щоб не зіпсувати комусь день смішним ADL-бізнесом. ( Edit рада Піт Беккер на цю тему )
sehe

2
@sehe: Якщо у вас лежить хеш-функтор, який, можливо, може бути сконструйований за замовчуванням, але чому? (Рівність є простішою, оскільки ви просто реалізуєте членство operator==.) Моя загальна філософія полягає в тому, що якщо функція є природною і по суті є єдиною "правильною" (як порівняння лексикографічної пари), то я додаю її std. Якщо це щось своєрідне (наприклад, неспівпорядковане порівняння пар), я роблю це специфічним для типу контейнера.
Керрек СБ

3
Я не погоджуюся, але де в стандарті нам дозволено та заохочують додавати спеціалізації до std?
razeh

3
@Kerrek, я згоден, але я сподівався на те, що в главі та вірші буде посилання на місце в стандарті. Я знайшов формулювання, що дозволяє робити ін'єкцію в 17.6.4.2.1, де сказано, що вона не дозволена "якщо не вказано інше", але мені не вдалося знайти частину "в іншому випадку" серед специфікації сторінки 4000+.
razeh

3
@razeh тут ви можете прочитати "Програма може додати спеціалізацію шаблонів для будь-якого стандартного шаблону бібліотеки до простору імен std, лише якщо декларація залежить від визначеного користувачем типу і спеціалізація відповідає стандартним вимогам бібліотеки до вихідного шаблону і явно не заборонена . ". Так що це рішення нормально.
Marek R

7

Моя ставка буде на аргумент шаблону Hash для класів unorряд_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>()
    • будь-який вираз зв’язку, що задовольняє підпис -

Я вдячний, що ви можете написати щось, що не має конструктора за замовчуванням, але я завжди вважаю, що вимагати, щоб кожна конструкція карти запам'ятовувала додатковий аргумент - це трохи тягар - зовсім небагато тягаря на мій смак. Я в порядку з явним аргументом шаблону, хоча спеціалізація std::hashвсе-таки є найприємнішим виходом :-)
Kerrek SB

визначені користувачем типи на допомогу :-) Більш серйозно, я сподіваюся, що ми вдаримо їх по зап'ястях вже на етапі, де їх клас містить char*!
Керрек СБ

Хм ... у вас є приклад того, як hashспеціалізація втручається через ADL? Я маю на увазі, це цілком правдоподібно, але мені важко підійти до проблемного випадку.
Керрек СБ


Це все розваги та ігри, поки вам не потрібно, std::unordered_map<Whatever, Xunset>і це не працює, тому що Xunsetтип вашого хеша не може бути сконструйований за замовчуванням.
Брайан Гордон

4

@ Керрек СБ охопив 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є значенням типу Key, то h(u)це дійсне вираження з типом, size_tякий не змінюється u.

Ні, жодна реалізація не відповідає стандартам, оскільки вони намагаються спеціалізуватися, std::hash<X>::operator()а не std::hash<X>в цілому, а підпис визначено std::hash<T>::operator()реалізацією.
ildjarn

@ildjarn: Уточнено - я говорив про впровадження бібліотеки, а не про спроби спеціалізації. Не впевнений, який саме ОП мав намір запитати.
aschepler
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.