std::hash‹T› ควรทำงานเมื่อ T นั้น std::pair‹สองประเภทที่ง่ายกว่านั้นรองรับโดย std::hash› หรือไม่

ฉันใช้ชุดคำสั่งที่ประกาศดังนี้:

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

หลังจากทำการวิเคราะห์วิธีที่ฉันใช้ set, แล้ว ฉันสรุปได้ว่า unordered_set จะเป็นตัวเลือกที่ชาญฉลาดกว่า แต่เมื่อฉันเปลี่ยน std::set เป็น std::unordered_set ฉันได้รับข้อความแสดงข้อผิดพลาดมากมายจากคอมไพเลอร์ของฉัน (g++ 4.8.1) โดยบ่นว่า

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

ฉันพบว่า std::hash ไม่ทราบวิธีจัดการกับประเภทที่เป็น std::pair แม้ว่าทั้งสองประเภทที่ประกอบเป็น pair นั้นแต่ละประเภทสามารถแฮชได้ก็ตาม ฉันคิดว่า ข้อผิดพลาดสำหรับฟังก์ชันแฮชของคู่ ints มีความเกี่ยวข้อง ข้อมูลเกี่ยวกับมาตรฐาน C++11 ที่อธิบายว่าทำไมสิ่งต่างๆ จึงผิดพลาด (ไม่มีคำอธิบายที่ดีสำหรับกำแพงข้อความแสดงข้อผิดพลาดที่ g++ ปล่อยออกมาสำหรับสิ่งนี้)

สำหรับฉันดูเหมือนว่า

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

โดยที่ some_func() อาจง่ายเหมือน XOR (หรือไม่ ดู ทำไม XOR เป็นวิธีเริ่มต้นในการรวมแฮชหรือไม่)

มีเหตุผลที่ดีหรือไม่ที่มาตรฐานไม่ต้องการให้ std::hash รู้วิธีสร้างค่าแฮชสำหรับออบเจ็กต์ซึ่งเป็นประเภท pair ที่สามารถแฮชแต่ละรายการได้


person jzions    schedule 14.01.2015    source แหล่งที่มา
comment
คุณแฮช const myClass * ได้ไหม? หรือคุณสามารถแฮช const myClass เท่านั้น?   -  person Dietrich Epp    schedule 14.01.2015
comment
มีข้อเสนอที่แก้ไขปัญหานี้ นอกจากนั้น คำถามนี้กว้างเกินไปและไม่สามารถตอบได้จริงๆ ในตอนนี้ คุณสามารถระบุความเชี่ยวชาญพิเศษ std::hash ของคุณเอง และใช้ boost::hash_combine เพื่อรวมแฮชแต่ละรายการได้   -  person Praetorian    schedule 14.01.2015
comment
โปรดใช้ซอฟต์แวร์เบื้องหลังข้อเสนอที่ Praetorian อ้างอิงถึง: github.com/HowardHinnant/hash_append เพียง ในช่วงสองสามวันที่ผ่านมา ทีมของฉันได้รับประโยชน์จากความสามารถในการเปรียบเทียบอัลกอริทึมแฮช X กับอัลกอริทึมแฮช Y ตามที่ใช้กับแอปพลิเคชันเฉพาะของเรา และเลือกอันที่ดีที่สุดสำหรับเรา   -  person Howard Hinnant    schedule 15.01.2015
comment
XOR เป็นวิธีที่แย่มากในการรวมแฮช   -  person Yakk - Adam Nevraumont    schedule 30.04.2015


คำตอบ (1)


เหตุผลง่ายๆ คือไม่ได้เพิ่มเข้าไปในมาตรฐาน เช่นเดียวกับการแฮชโครงสร้างอื่นๆ เช่น tuple

สิ่งต่างๆ มักจะถูกเพิ่มเข้าไปในมาตรฐานเมื่อมันดีพอ ไม่ใช่เมื่อมันสมบูรณ์แบบ เพราะความสมบูรณ์แบบเป็นศัตรูของความดี ความเชี่ยวชาญพิเศษของ std::hash ไม่ใช่สิ่งที่จะทำให้โค้ดเสียหาย (บ่อยครั้ง) ดังนั้นการเพิ่มสิ่งใหม่จึงไม่เป็นอันตราย

ไม่ว่าในกรณีใด เราสามารถเขียนส่วนขยายแฮชของเราเองได้ ตัวอย่างเช่น:

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); }
  }
}

และตอนนี้ hashers::hash<T> จะใช้ฟังก์ชัน custom_hash ที่ค้นหาโดย ADL ซ้ำๆ หรือ std::hash หากล้มเหลว เพื่อแฮช T และส่วนประกอบต่างๆ และ hashers::hash<> จะเป็นแฮชสากลที่พยายามแฮชอะไรก็ตามที่ส่งผ่านไป

โค้ดอาจไม่คอมไพล์ตามที่แสดง

ฉันเลือกที่จะแฮชคอนเทนเนอร์และทูเปิลทั้งหมดตามความยาวแฮช ตามด้วยการแฮชการรวมเนื้อหาเข้าด้วยกัน ผลข้างเคียง array<int, 3> แฮชเหมือนกับ tuple<int,int,int> และ tuple<int,int> แฮชเหมือนกับ pair<int,int> และ std::vector<char>{'a','b','c', '\0'} แฮชเหมือนกับ "abc" ซึ่งฉันคิดว่าเป็นคุณสมบัติที่ดี อาร์เรย์ว่าง/tuple/vector/etc แฮชเช่น size_t(0)

คุณสามารถขยายระบบข้างต้นสำหรับประเภทของคุณเองได้โดยเพียงแค่แทนที่ custom_hash ในเนมสเปซของประเภทที่ต้องการ หรือเชี่ยวชาญ std::hash<X> หรือ hashers::hash<X> เพื่อทำแฮชที่คุณกำหนดเอง (ฉันจะใช้ std::hash สำหรับหลักการที่ทำให้ตัวเองประหลาดใจน้อยที่สุด) สำหรับการใช้งานขั้นสูง คุณสามารถเชี่ยวชาญ hashers::details::hash<X,void> ด้วย SFINAE ได้ แต่ฉันบอกให้ทำเพื่อ custom_hash แทน

person Yakk - Adam Nevraumont    schedule 14.01.2015
comment
เฟรมเวิร์กการแฮชที่ดีกว่า ไม่จำเป็นต้องรวมแฮช: open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3980.html - person Maxim Egorushkin; 05.10.2020
comment
ข้อแม้: การเขียนแฮชแบบกำหนดเองสำหรับคอนเทนเนอร์ที่ไม่ได้เรียงลำดับควรเคารพสิ่งที่ไม่สำคัญ == - person Caleth; 05.10.2020
comment
@Caleth ฉันไม่แน่ใจว่า Caveat นั้นกำลังพูดถึงอยู่ที่ไหน หากคอนเทนเนอร์ที่ไม่ได้เรียงลำดับสองรายการส่งคืน == จริง แฮชของคุณจะต้องเหมือนกัน แต่ฉันไม่เห็นคำแนะนำหรือการใช้งานด้านบนที่ไม่เห็นด้วยกับสิ่งนั้น คุณช่วยชี้ให้เห็นได้ไหม? - person Yakk - Adam Nevraumont; 05.10.2020
comment
ความไร้เดียงสา hash_combine_range(unordered.begin(), unordered.end()) นั้นไม่จำเป็นเสมอไป - person Caleth; 05.10.2020
comment
@caleth อ่า เพราะคอนเทนเนอร์สองอันที่ไม่ได้เรียงลำดับซึ่งมีที่เก็บข้อมูลต่างกันจะวนซ้ำต่างกัน - person Yakk - Adam Nevraumont; 05.10.2020