Làm thế nào để đặc biệt hóa std :: hash <Key> :: operator () cho kiểu do người dùng xác định trong các vùng chứa không có thứ tự?


101

Để hỗ trợ các loại khóa do người dùng xác định trong std::unordered_set<Key>std::unordered_map<Key, Value> người ta phải cung cấp operator==(Key, Key)và một bộ chức năng băm:

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;

Sẽ thuận tiện hơn nếu chỉ viết std::unordered_set<X> với một hàm băm mặc định cho kiểu X, như cho các kiểu đi kèm với trình biên dịch và thư viện. Sau khi tư vấn

có vẻ như có thể chuyên môn hóa 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 
}                                                                             

Hỗ trợ trình biên dịch cho C ++ 11 vẫn chưa được thử nghiệm --- Tôi đã không thử Clang ---, đây là những câu hỏi của tôi:

  1. Việc thêm một chuyên ngành như vậy vào không gian tên có hợp pháp không std? Tôi có cảm xúc lẫn lộn về điều đó.

  2. std::hash<X>::operator()Phiên bản nào trong số các phiên bản, nếu có, tuân thủ tiêu chuẩn C ++ 11?

  3. Có một cách di động để làm điều đó?


Với gcc 4.7.2, tôi đã phải cung cấp một toàn cầuoperator==(const Key, const Key)
Victor Lyuboslavsky

Lưu ý rằng hướng dẫn kiểu của Google không khuyến khích việc chuyên môn hóa std::hash(không giống như những thứ khác trong stdkhông gian tên) ; Mang nó theo một hạt muối.
Franklin Yu

Câu trả lời:


128

Bạn được phép rõ ràng và được khuyến khích thêm chuyên môn vào không gian tên std*. Cách chính xác (và về cơ bản là duy nhất) để thêm hàm băm là:

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

(Các chuyên môn phổ biến khác mà bạn có thể cân nhắc hỗ trợ std::less, std::equal_tostd::swap.)

*) miễn là một trong các loại liên quan do người dùng xác định, tôi cho là vậy.


3
trong khi điều này là có thể, nói chung bạn có khuyên bạn nên làm theo cách này không? unorder_map<eltype, hash, equality>Thay vào đó, tôi muốn viết nhanh hơn , để tránh làm hỏng một ngày của ai đó bằng công việc kinh doanh ADL vui nhộn. ( Chỉnh sửa lời khuyên của Pete Becker về chủ đề này )
vào

2
@sehe: Nếu bạn có một hàm băm nằm xung quanh đó là cấu trúc mặc định, có lẽ, nhưng tại sao? (Bình đẳng dễ dàng hơn, vì bạn chỉ cần triển khai thành viên- operator==.) Triết lý chung của tôi là nếu hàm là tự nhiên và về cơ bản là hàm duy nhất "đúng" (như so sánh cặp từ vựng), thì tôi thêm nó vào std. Nếu đó là một cái gì đó đặc biệt (như so sánh cặp không có thứ tự), thì tôi làm cho nó cụ thể cho một loại vùng chứa.
Kerrek SB

3
Tôi không đồng ý, nhưng chúng ta được phép và khuyến khích thêm các chuyên môn vào std ở đâu trong tiêu chuẩn?
razeh

3
@Kerrek, tôi đồng ý, nhưng tôi đã hy vọng tham chiếu chương và câu đến một vị trí trong tiêu chuẩn. Tôi đã tìm thấy từ ngữ cho phép chèn tại 17.6.4.2.1 trong đó nó nói rằng nó không được phép "trừ khi được chỉ định khác", nhưng tôi không thể tìm thấy phần "được chỉ định khác" trong đặc điểm kỹ thuật hơn 4000 trang.
razeh

3
@razeh tại đây bạn có thể đọc "Một chương trình có thể thêm chuyên môn hóa mẫu cho bất kỳ mẫu thư viện tiêu chuẩn nào vào không gian tên std chỉ khi khai báo phụ thuộc vào kiểu do người dùng xác định và chuyên ngành đáp ứng các yêu cầu thư viện tiêu chuẩn cho mẫu gốc và không bị cấm một cách rõ ràng . ”. Vì vậy, giải pháp này là ok.
Marek R

7

Đặt cược của tôi sẽ là đối số mẫu băm cho các lớp unardered_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
}

Tất nhiên

  • hashX cũng có thể là một hàm tĩnh toàn cầu
  • trong trường hợp thứ hai, bạn có thể vượt qua điều đó
    • đối tượng functor cũ ( struct Xhasher { size_t operator(const X&) const; };)
    • std::hash<X>()
    • bất kỳ biểu thức ràng buộc nào thỏa mãn chữ ký -

Tôi đánh giá cao việc bạn có thể viết một cái gì đó không có hàm tạo mặc định, nhưng tôi luôn thấy rằng việc yêu cầu mỗi lần xây dựng bản đồ phải nhớ đối số bổ sung là một chút gánh nặng - hơi quá sức đối với sở thích của tôi. Tôi OK với mẫu đối rõ ràng, mặc dù chuyên std::hashvẫn là con đường thoát khỏi đẹp nhất :-)
Kerrek SB

các kiểu do người dùng xác định để giải cứu :-) Nghiêm trọng hơn, tôi hy vọng chúng ta sẽ tát chúng vào cổ tay đã ở giai đoạn mà lớp của chúng chứa a char*!
Kerrek SB

Hmm ... bạn có ví dụ về cách một hashchuyên môn can thiệp qua ADL không? Ý tôi là, điều đó hoàn toàn chính đáng, nhưng tôi gặp khó khăn khi đưa ra một trường hợp vấn đề.
Kerrek SB


Đó là tất cả các trò chơi và thú vị cho đến khi bạn cần std::unordered_map<Whatever, Xunset>và nó không hoạt động vì Xunsetloại băm của bạn không được xây dựng mặc định.
Brian Gordon

4

@Kerrek SB đã bao gồm 1) và 3).

2) Mặc dù g ++ và VC10 khai báo std::hash<T>::operator()với các chữ ký khác nhau, cả hai triển khai thư viện đều tuân thủ Tiêu chuẩn.

Tiêu chuẩn không chỉ định các thành viên của std::hash<T>. Nó chỉ nói rằng mỗi chuyên ngành như vậy phải đáp ứng cùng một yêu cầu "Hash" cần thiết cho đối số mẫu thứ hai std::unordered_set, v.v. Cụ thể:

  • Kiểu băm Hlà một đối tượng hàm, có ít nhất một kiểu đối số Key.
  • H là bản sao có thể xây dựng.
  • H là có thể phá hủy.
  • Nếu hlà một biểu thức của kiểu Hhoặc const Hklà một biểu thức của kiểu có thể chuyển đổi thành (có thể const) Key, thì h(k)là một biểu thức hợp lệ với kiểu size_t.
  • Nếu hlà một biểu thức của kiểu Hhoặc const Hulà giá trị của kiểu Key, thì đó h(u)là một biểu thức hợp lệ với kiểu size_tkhông sửa đổi u.

Không, không có triển khai nào là tuân thủ tiêu chuẩn, vì chúng cố gắng chuyên môn hóa std::hash<X>::operator()hơn std::hash<X>là toàn bộ và chữ ký của std::hash<T>::operator()là do triển khai xác định.
ildjarn

@ildjarn: Đã làm rõ - Tôi đang nói về việc triển khai thư viện, không phải các chuyên môn đã cố gắng. Không chắc OP muốn hỏi chính xác điều gì.
aschepler
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.