Bagaimana cara mengkhususkan std :: hash <Key> :: operator () untuk tipe yang ditentukan pengguna dalam wadah tak berurutan?

101

Untuk mendukung jenis kunci yang ditentukan pengguna std::unordered_set<Key>dan std::unordered_map<Key, Value> harus menyediakan operator==(Key, Key)dan fungsi 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;

Akan lebih mudah untuk menulis hanya std::unordered_set<X> dengan hash default untuk tipe X, seperti untuk tipe yang disertakan dengan kompilator dan pustaka. Setelah berkonsultasi

tampaknya mungkin untuk mengkhususkan 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 
}                                                                             

Mengingat dukungan compiler untuk C ++ 11 masih bersifat eksperimental --- Saya tidak mencoba Clang ---, ini adalah pertanyaan saya:

  1. Apakah legal menambahkan spesialisasi seperti itu ke namespace std? Saya memiliki perasaan campur aduk tentang itu.

  2. std::hash<X>::operator()Versi manakah , jika ada, yang sesuai dengan standar C ++ 11?

  3. Apakah ada cara portabel untuk melakukannya?

René Richter
sumber
Dengan gcc 4.7.2, saya harus menyediakan globaloperator==(const Key, const Key)
Victor Lyuboslavsky
Perhatikan bahwa spesialisasi std::hash(tidak seperti hal lain dalam stdnamespace) tidak disarankan oleh panduan gaya Google ; ambillah dengan sebutir garam.
Franklin Yu

Jawaban:

128

Anda secara tegas diizinkan dan didorong untuk menambahkan spesialisasi ke namespace std*. Cara yang benar (dan pada dasarnya hanya) untuk menambahkan fungsi hash adalah ini:

namespace std {
  template <> struct hash<Foo>
  {
    size_t operator()(const Foo & x) const
    {
      /* your code here, e.g. "return hash<int>()(x.value);" */
    }
  };
}

(Spesialisasi populer lainnya yang mungkin Anda pertimbangkan untuk didukung adalah std::less, std::equal_todan std::swap.)

*) selama salah satu tipe yang terlibat ditentukan oleh pengguna, saya kira.

Kerrek SB
sumber
3
sementara ini memungkinkan, apakah Anda secara umum menyarankan untuk melakukannya dengan cara ini? Saya lebih suka instantiation unorder_map<eltype, hash, equality>, untuk menghindari merusak hari seseorang dengan bisnis ADL yang lucu. ( Edit nasihat Pete Becker tentang topik ini )
lihat
2
@sehe: Jika Anda memiliki fungsi hash yang dapat dibangun secara default, mungkin, tapi mengapa? (Kesetaraan lebih mudah, karena Anda baru saja mengimplementasikan anggota- operator==.) Filosofi umum saya adalah jika fungsinya natural dan pada dasarnya satu-satunya yang "benar" (seperti perbandingan pasangan leksikografik), maka saya menambahkannya ke std. Jika itu sesuatu yang aneh (seperti perbandingan pasangan tidak berurutan), maka saya membuatnya khusus untuk jenis wadah.
Kerrek SB
3
Saya tidak setuju, tetapi di mana dalam standar kita diizinkan dan didorong untuk menambahkan spesialisasi ke std?
razeh
3
@Kerrek, saya setuju, tetapi saya berharap ada referensi pasal dan ayat ke suatu tempat dalam standar. Saya menemukan kata-kata yang memungkinkan injeksi di 17.6.4.2.1 yang mengatakan bahwa itu tidak diizinkan "kecuali ditentukan lain", tetapi saya belum dapat menemukan bagian "yang ditentukan lain" di tengah spesifikasi halaman 4000+.
razeh
3
@razeh di sini Anda dapat membaca "Program dapat menambahkan spesialisasi templat untuk templat pustaka standar apa pun ke namespace std hanya jika deklarasi bergantung pada tipe yang ditentukan pengguna dan spesialisasi memenuhi persyaratan pustaka standar untuk templat asli dan tidak secara eksplisit dilarang . ". Jadi solusi ini baik-baik saja.
Marek R
7

Taruhan saya akan berada pada argumen template Hash untuk kelas 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
}

Tentu saja

  • hashX juga bisa menjadi fungsi statis global
  • dalam kasus kedua, Anda bisa melewati itu
    • objek functor kuno ( struct Xhasher { size_t operator(const X&) const; };)
    • std::hash<X>()
    • ekspresi ikatan apa pun yang memenuhi tanda tangan -
lihat
sumber
Saya menghargai bahwa Anda dapat menulis sesuatu yang tidak memiliki konstruktor default, tetapi saya selalu menemukan bahwa mengharuskan setiap konstruksi peta untuk mengingat argumen tambahan sedikit membebani - agak terlalu membebani selera saya. Saya baik-baik saja dengan argumen template eksplisit, meskipun spesialisasi std::hashmasih merupakan jalan keluar terbaik :-)
Kerrek SB
tipe yang ditentukan pengguna untuk menyelamatkan :-) Lebih serius lagi, saya harap kami sudah menampar mereka di pergelangan tangan pada tahap di mana kelas mereka berisi char*!
Kerrek SB
Hmm ... apakah Anda punya contoh bagaimana hashspesialisasi mengganggu ADL? Maksud saya, ini sepenuhnya masuk akal, tetapi saya kesulitan untuk menemukan kasus masalah.
Kerrek SB
Semuanya menyenangkan dan permainan sampai Anda membutuhkan std::unordered_map<Whatever, Xunset>dan itu tidak berfungsi karena Xunsetjenis hasher Anda tidak dapat dibangun secara default.
Brian Gordon
4

@Kerrek SB telah meliput 1) dan 3).

2) Meskipun g ++ dan VC10 mendeklarasikan std::hash<T>::operator()dengan tanda tangan berbeda, kedua implementasi library tersebut sesuai dengan Standar.

Standar tidak menentukan anggota std::hash<T>. Ini hanya mengatakan bahwa setiap spesialisasi tersebut harus memenuhi persyaratan "Hash" yang sama yang diperlukan untuk argumen template kedua std::unordered_setdan seterusnya. Yaitu:

  • Jenis hash Hadalah objek fungsi, dengan setidaknya satu jenis argumen Key.
  • H adalah salinan dapat dibangun.
  • H dapat dirusak.
  • Jika hmerupakan ekspresi tipe Hatau const H, dan kmerupakan ekspresi tipe yang dapat diubah menjadi (mungkin const) Key, maka h(k)ekspresi valid dengan tipe size_t.
  • Jika hmerupakan ekspresi tipe Hatau const H, dan umerupakan nilai tipe l Key, maka h(u)ekspresi valid dengan tipe size_tyang tidak dimodifikasi u.
aschepler
sumber
Tidak, tidak ada implementasi yang memenuhi standar, karena mereka mencoba untuk mengkhususkan std::hash<X>::operator()daripada std::hash<X>secara keseluruhan, dan tanda tangan dari std::hash<T>::operator()adalah implementasi-didefinisikan.
ildjarn
@ildjarn: Klarifikasi - Saya berbicara tentang implementasi perpustakaan, bukan spesialisasi percobaan. Tidak yakin OP yang mana yang ingin ditanyakan.
aschepler