Haruskah std::hash‹T› berfungsi ketika T adalah std::pair‹dua tipe sederhana yang juga didukung oleh std::hash›?

Saya menggunakan set pesanan yang dinyatakan sebagai berikut:

std::set<std::pair<const std::string, const myClass *> > myset;

Setelah melakukan beberapa analisis terhadap cara saya menggunakan set, saya menyimpulkan bahwa unordered_set akan menjadi pilihan yang lebih cerdas. Tetapi ketika saya mengubah std::set menjadi std::unordered_set, saya mendapat banyak pesan kesalahan dari kompiler saya (g++ 4.8.1) yang mengeluhkan kesalahan

invalid use of incomplete type struct std::hash<std::pair<const std::basic_string<char>, const myClass * > >

Saya menemukan bahwa std::hash tidak tahu bagaimana menangani tipe yang std::pair, meskipun faktanya dua tipe yang membentuk pair masing-masing dapat di-hash. Saya pikir kesalahan untuk fungsi hash dari pasangan int mengandung relevansi informasi tentang standar C++11 yang menjelaskan mengapa semuanya menjadi kacau. (Tidak ada penjelasan yang baik untuk dinding teks kesalahan yang tidak dapat ditembus yang dikeluarkan g++ untuk ini.)

Bagi saya tampaknya demikian

std::hash<std::pair<T1, T2>> hasher(make_pair(x,y))
  = some_func(std::hash<T1>hasher(x), std::hash<T2>hasher(y) )

di mana some_func() bisa sesederhana XOR (atau tidak; lihat Mengapa apakah XOR merupakan cara default untuk menggabungkan hash?)

Apakah ada alasan bagus bagi standar untuk tidak mengharuskan std::hash mengetahui cara membuat nilai hash untuk objek yang merupakan pair tipe yang masing-masing dapat di-hash?


person jzions    schedule 14.01.2015    source sumber
comment
Bisakah Anda meng-hash const myClass *? Atau bisakah Anda hanya melakukan hash const myClass?   -  person Dietrich Epp    schedule 14.01.2015
comment
Ada proposal yang mengatasi masalah ini. Selain itu, pertanyaan ini terlalu luas dan tidak dapat dijawab. Untuk saat ini, Anda dapat memberikan std::hash spesialisasi Anda sendiri dan menggunakan boost::hash_combine untuk menggabungkan masing-masing hash.   -  person Praetorian    schedule 14.01.2015
comment
Silakan gunakan perangkat lunak di balik proposal yang dirujuk Praetorian: github.com/HowardHinnant/hash_append Just dalam beberapa hari terakhir tim saya mendapat manfaat dari kemampuan untuk dengan mudah membandingkan algoritma hash X dengan algoritma hash Y, sebagaimana diterapkan pada aplikasi spesifik kami, dan memilih mana yang terbaik untuk kami.   -  person Howard Hinnant    schedule 15.01.2015
comment
XOR adalah cara jelek untuk menggabungkan hash   -  person Yakk - Adam Nevraumont    schedule 30.04.2015


Jawaban (1)


Alasannya sederhana, tidak ditambahkan standar. Hal yang sama berlaku untuk hashing struktur lain seperti tuple.

Sesuatu cenderung ditambahkan ke standar ketika sudah cukup baik, bukan ketika sudah sempurna, karena kesempurnaan adalah musuh kebaikan. Lebih banyak spesialisasi std::hash bukanlah hal yang akan memecahkan kode (sering terjadi), jadi menambahkan yang baru relatif tidak berbahaya.

Bagaimanapun, untuk tujuan itu, kita dapat menulis ekstender hash kita sendiri. Sebagai contoh:

namespace hashers {
  constexpr size_t hash_combine( size_t, size_t ); // steal from boost, or write your own
  constexpr size_t hash_combine( size_t a ) { return a; }
  constexpr size_t hash_combine() { return 0; }
  template<class...Sizes>
  constexpr size_t hash_combine( size_t a, size_t b, Sizes... sizes ) {
    return hash_combine( hash_combine(a,b), sizes... );
  }

  template<class T=void> struct hash;

  template<class A, class B>
  constexpr size_t custom_hash( std::pair<A,B> const& p ) {
    return hash_combine( hash<size_t>{}(2), hash<std::decay_t<A>>{}(p.first), hash<std::decay_t<B>>{}(p.second) );
  }
  template<class...Ts, size_t...Is>
  constexpr size_t custom_hash( std::index_sequence<Is...>, std::tuple<Ts...> const& p ) {
    return hash_combine( hash<size_t>{}(sizeof...(Ts)), hash<std::decay_t<Ts>>{}(std::get<Is>(p))... );
  }
  template<class...Ts>
  constexpr size_t custom_hash( std::tuple<Ts...> const& p ) {
    return custom_hash( std::index_sequence_for<Ts...>{}, p );
  }
  template<class T0, class C>
  constexpr size_t custom_hash_container( size_t n, C const& c) {
    size_t retval = hash<size_t>{}(n);
    for( auto&& x : c)
      retval = hash_combine( retval, hash<T>{}(x) );
    return retval;
  }
  template<class T0, class C>
  constexpr size_t custom_hash_container( C const& c) {
    return custom_hash_container( c.size(), c );
  }
  template<class T, class...Ts>
  size_t custom_hash( std::vector<T, Ts...> const& v ) {
    return custom_hash_container<T>(v);
  }
  template<class T, class...Ts>
  size_t custom_hash( std::basic_string<T, Ts...> const& v ) {
    return custom_hash_container<T>(v);
  }
  template<class T, size_t n>
  constexpr size_t custom_hash( std::array<T, n> const& v ) {
    return custom_hash_container<T>(n, v);
  }
  template<class T, size_t n>
  constexpr size_t custom_hash( T (const& v)[n] ) {
    return custom_hash_container<T>(n, v);
  }
  // etc -- list, deque, map, unordered map, whatever you want to support
  namespace details {
    template<class T, class=void>
    struct hash : std::hash<T> {};
    using hashers::custom_hash;
    template<class T>
    struct hash<T,decltype(void(
      custom_hash(declval<T const&>())
    )) {
      constexpr size_t operator()(T const& t)const {
        return custom_hash(t);
      }
    };
  }
  template<class T>
  struct hash : details::hash<T> {};
  template<>
  struct hash<void> {
    template<class T>
    constexpr size_t operator()(T const& t)const { return hash<T>{}(t); }
  }
}

dan sekarang hashers::hash<T> akan secara rekursif menggunakan fungsi custom_hash yang dicari ADL, atau std::hash jika gagal, untuk melakukan hash T dan komponennya, dan hashers::hash<> adalah hasher universal yang mencoba melakukan hash apa pun yang diteruskan ke fungsi tersebut.

Kode mungkin tidak dapat dikompilasi seperti yang ditunjukkan.

Saya memilih untuk meng-hash semua kontainer dan tupel sebagai hash panjangnya, diikuti dengan meng-hash kombinasi isinya. Sebagai efek sampingnya, array<int, 3> hashnya sama dengan tuple<int,int,int>, dan tuple<int,int> hashnya sama dengan pair<int,int>, dan std::vector<char>{'a','b','c', '\0'} hashnya sama dengan "abc", yang menurut saya merupakan properti yang bagus. Hash array/tuple/vector/etc kosong seperti size_t(0).

Anda dapat memperluas sistem di atas untuk tipe Anda sendiri hanya dengan mengganti custom_hash di namespace tipe yang dimaksud, atau mengkhususkan std::hash<X> atau hashers::hash<X> untuk melakukan hash khusus Anda (saya akan menggunakan std::hash untuk prinsip yang paling tidak mengejutkan saya sendiri). Untuk penggunaan tingkat lanjut, Anda dapat mengkhususkan hashers::details::hash<X,void> dengan SFINAE, tapi menurut saya lakukan untuk custom_hash saja.

person Yakk - Adam Nevraumont    schedule 14.01.2015
comment
Kerangka kerja hashing yang lebih baik, tidak perlu menggabungkan hash: open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3980.html - person Maxim Egorushkin; 05.10.2020
comment
Peringatan: menulis hash khusus untuk kontainer yang tidak diurutkan harus menghormati == nontrivialnya - person Caleth; 05.10.2020
comment
@Caleth Saya tidak yakin ke mana tujuan Peringatan itu. Jika dua kontainer yang tidak berurutan menghasilkan == true, hash Anda harus sama, tetapi saya tidak melihat saran atau implementasi apa pun di atas yang tidak sesuai dengan itu. Bisakah Anda menunjukkannya? - person Yakk - Adam Nevraumont; 05.10.2020
comment
hash_combine_range(unordered.begin(), unordered.end()) yang naif belum tentu berhasil - person Caleth; 05.10.2020
comment
@caleth ah, karena dua kontainer tidak berurutan dengan jumlah ember berbeda melakukan iterasi yang berbeda. - person Yakk - Adam Nevraumont; 05.10.2020