จะเชี่ยวชาญ std :: hash <Key> :: operator () สำหรับชนิดที่ผู้ใช้กำหนดในคอนเทนเนอร์ที่ไม่ได้เรียงลำดับได้อย่างไร


101

ในการรองรับประเภทคีย์ที่ผู้ใช้กำหนดstd::unordered_set<Key>และstd::unordered_map<Key, Value> ต้องระบุoperator==(Key, Key)และแฮช functor:

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 ยังเป็นการทดลอง - ฉันไม่ได้ลองเสียงดัง --- นี่คือคำถามของฉัน:

  1. การเพิ่มความเชี่ยวชาญดังกล่าวให้กับเนมสเปซถูกกฎหมายstdหรือไม่? ฉันมีความรู้สึกหลากหลายเกี่ยวกับเรื่องนั้น

  2. ซึ่งในstd::hash<X>::operator()รุ่นถ้ามีสอดคล้องกับ C ++ 11 มาตรฐาน?

  3. มีวิธีทำแบบพกพาไหม


ด้วย gcc 4.7.2 ฉันต้องให้ทั่วโลกoperator==(const Key, const Key)
Victor Lyuboslavsky

หมายเหตุ: ความเชี่ยวชาญของที่std::hash(ไม่เหมือนสิ่งอื่น ๆ ในstdnamespace) จะท้อแท้ตามคู่มือสไตล์ Google ; นำไปผสมกับเกลือหนึ่งเม็ด
Franklin Yu

คำตอบ:


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 ตลก ๆ ( แก้ไข คำแนะนำของ Pete Becker ในหัวข้อนี้ )
เห็น

2
@sehe: หากคุณมีแฮช functor ที่อยู่รอบ ๆ ซึ่งเป็นค่าเริ่มต้นที่สร้างได้บางที แต่ทำไม? (ความเท่าเทียมกันเป็นเรื่องง่ายเนื่องจากคุณต้องการเพียงแค่ใช้ member- operator==.) ปรัชญาทั่วไปฉันคือว่าถ้าฟังก์ชั่นเป็นไปตามธรรมชาติและเป็นหลักเท่านั้น "ถูกต้อง" หนึ่ง (เช่นการเปรียบเทียบคู่พจนานุกรม) stdแล้วผมเพิ่มเข้าไป หากเป็นสิ่งที่แปลก (เช่นการเปรียบเทียบคู่ที่ไม่เรียงลำดับ) ฉันจะกำหนดให้เป็นประเภทคอนเทนเนอร์โดยเฉพาะ
Kerrek SB

3
ฉันไม่เห็นด้วย แต่เราได้รับอนุญาตและสนับสนุนให้เพิ่มความเชี่ยวชาญพิเศษในมาตรฐานตรงไหน?
razeh

3
@Kerrek ฉันเห็นด้วย แต่ฉันหวังว่าจะมีการอ้างอิงบทและกลอนไปยังสถานที่ในมาตรฐาน ฉันพบข้อความที่อนุญาตให้ฉีดที่ 17.6.4.2.1 ซึ่งระบุว่าไม่อนุญาต "เว้นแต่จะระบุไว้เป็นอย่างอื่น" แต่ฉันไม่พบส่วนที่ "ระบุไว้เป็นอย่างอื่น" ท่ามกลางข้อกำหนดหน้า 4000+
razeh

3
@razeh ที่นี่คุณสามารถอ่าน "โปรแกรมอาจเพิ่มความเชี่ยวชาญเทมเพลตสำหรับเทมเพลตไลบรารีมาตรฐานใด ๆ ลงในเนมสเปซ std ก็ต่อเมื่อการประกาศขึ้นอยู่กับประเภทที่ผู้ใช้กำหนดและความเชี่ยวชาญตรงตามข้อกำหนดไลบรารีมาตรฐานสำหรับเทมเพลตดั้งเดิมและไม่ได้ห้ามอย่างชัดเจน . ". วิธีนี้ก็โอเค
Marek R

7

เดิมพันของฉันคืออาร์กิวเมนต์แม่แบบแฮชสำหรับคลาส 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 อาจเป็นฟังก์ชันคงที่ทั่วโลกได้เช่นกัน
  • ในกรณีที่สองคุณสามารถผ่านมันไปได้
    • วัตถุ functor สมัยเก่า ( struct Xhasher { size_t operator(const X&) const; };)
    • std::hash<X>()
    • นิพจน์ผูกใด ๆ ที่ตรงตามลายเซ็น -

ฉันขอขอบคุณที่คุณสามารถเขียนบางสิ่งที่ไม่มีตัวสร้างเริ่มต้นได้ แต่ฉันมักจะพบว่าการกำหนดให้การสร้างแผนที่แต่ละครั้งต้องจำอาร์กิวเมนต์เพิ่มเติมนั้นเป็นภาระเล็กน้อย - ค่อนข้างเป็นภาระสำหรับรสนิยมของฉันมากเกินไป ฉันพอใจกับอาร์กิวเมนต์แม่แบบที่ชัดเจนแม้ว่าความเชี่ยวชาญstd::hashยังคงเป็นวิธีที่ดีที่สุด :-)
Kerrek SB

ประเภทที่ผู้ใช้กำหนดเพื่อช่วยเหลือ :-) อย่างจริงจังยิ่งกว่านั้นฉันหวังว่าเราจะตบข้อมือพวกเขาในขั้นตอนที่คลาสของพวกเขามีchar*!
Kerrek SB

อืม ... คุณมีตัวอย่างว่าความhashเชี่ยวชาญรบกวนผ่าน ADL หรือไม่? ฉันหมายความว่ามันเป็นไปได้ทั้งหมด แต่ฉันมีช่วงเวลาที่ยากลำบากในการหากรณีปัญหา
Kerrek SB


มันสนุกและเป็นเกมจนกว่าคุณจะต้องการstd::unordered_map<Whatever, Xunset>และมันใช้ไม่ได้เพราะXunsetประเภทแฮชของคุณไม่ได้เป็นค่าเริ่มต้นที่สร้างได้
Brian Gordon

4

@Kerrek SB ครอบคลุม 1) และ 3)

2) แม้ว่า g ++ และ VC10 จะประกาศstd::hash<T>::operator()ด้วยลายเซ็นที่ต่างกัน แต่การใช้งานไลบรารีทั้งสองก็เป็นไปตามมาตรฐาน

มาตรฐานไม่ได้ระบุสมาชิกของstd::hash<T>. เพียงแค่บอกว่าความเชี่ยวชาญแต่ละอย่างจะต้องเป็นไปตามข้อกำหนด "Hash" เดียวกันที่จำเป็นสำหรับอาร์กิวเมนต์แม่แบบที่สองstd::unordered_setเป็นต้น ได้แก่ :

  • ประเภทกัญชาเป็นวัตถุที่ฟังก์ชั่นที่มีประเภทการโต้แย้งอย่างน้อยหนึ่งHKey
  • H เป็นสำเนาที่สร้างสรรค์ได้
  • H เป็นสิ่งที่ทำลายได้
  • หากhคือการแสดงออกของประเภทHหรือconst Hและkคือการแสดงออกของชนิดแปลงสภาพที่ (อาจจะconst) Keyแล้วคือการแสดงออกที่ถูกต้องกับประเภทh(k)size_t
  • หากhคือการแสดงออกของประเภทHหรือconst Hและuเป็น lvalue ประเภทKeyแล้วh(u)คือการแสดงออกที่ถูกต้องกับประเภทที่ไม่ได้ปรับเปลี่ยนsize_tu

ไม่การใช้งานไม่เป็นไปตามมาตรฐานเนื่องจากพวกเขาพยายามที่จะเชี่ยวชาญstd::hash<X>::operator()มากกว่าstd::hash<X>โดยรวมและลายเซ็นของstd::hash<T>::operator()ถูกกำหนดไว้สำหรับการนำไปใช้งาน
ildjarn

@ildjarn: ชี้แจง - ฉันกำลังพูดถึงการใช้งานไลบรารีไม่ใช่ความเชี่ยวชาญพิเศษที่พยายาม ไม่แน่ใจว่า OP หมายถึงถามอะไร
aschepler
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.