Sırasız kapsayıcılarda kullanıcı tanımlı tür için std :: hash <Key> :: operator () nasıl uzmanlaşır?


101

Kullanıcı tanımlı anahtar türlerini desteklemek std::unordered_set<Key>ve std::unordered_map<Key, Value> bir sağlamak zorundadır operator==(Key, Key)ve bir karma funktoru:

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;

Derleyici ve kitaplıkla birlikte gelen türlerde olduğu gibi, yalnızca tür için varsayılan bir hashstd::unordered_set<X> ile yazmak daha uygun olacaktır . Danıştıktan sonraX

  • C ++ Standart Taslak N3242 §20.8.12 [unord.hash] ve §17.6.3.4 [hash.requirements],
  • Arttırılmamış
  • g ++ include\c++\4.7.0\bits\functional_hash.h
  • VC10 include\xfunctional
  • Stack Overflow'daki çeşitli ilgili sorular

uzmanlaşmak mümkün görünüyor 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 için derleyici desteği verildiğinde henüz deneysel - Clang'ı denemedim -, bunlar benim sorularım:

  1. Ad alanına böyle bir uzmanlık eklemek yasal mı std? Bununla ilgili karışık hislerim var.

  2. std::hash<X>::operator()Varsa sürümlerden hangisi C ++ 11 standardıyla uyumludur?

  3. Bunu yapmanın taşınabilir bir yolu var mı?


Gcc 4.7.2 ile küresel bir bilgi sağlamalıydımoperator==(const Key, const Key)
Victor Lyuboslavsky

Uzmanlaşmanın std::hash( stdad alanındaki diğer şeylerin aksine ) Google stil kılavuzu tarafından önerilmediğini unutmayın ; bir tuz tanesi ile al.
Franklin Yu

Yanıtlar:


128

Sen açıkça izin verilmiş ve eklemek için teşvik edilir uzmanlık ad std*. Bir karma işlevi eklemenin doğru (ve temelde yalnızca) yolu şudur:

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

(Desteklemeyi düşünebileceğiniz diğer popüler uzmanlıklar std::less, std::equal_tove std::swap.)

*) ilgili türlerden biri kullanıcı tanımlı olduğu sürece, sanırım.


3
bu mümkün olsa da, genel olarak bu şekilde yapılmasını önerir misiniz? Bunun unorder_map<eltype, hash, equality>yerine, komik ADL işiyle birinin gününü mahvetmekten kaçınmak için örneklemeyi tercih ederim . ( Düzen bu konuda Pete Becker tavsiyesi )
sehe

2
@sehe: Etrafta yatan ve varsayılan olarak oluşturulabilen bir hash functor'unuz varsa, belki, ama neden? (Eşitlik daha kolaydır, çünkü sadece üye- uygulayacaksınız operator==.) Genel felsefem, eğer fonksiyon doğalsa ve esasen tek "doğru" ise (sözlükbilimsel çift karşılaştırması gibi), o zaman onu eklerim std. Tuhaf bir şeyse (sırasız çift karşılaştırması gibi), o zaman onu bir konteyner türüne özgü yapıyorum.
Kerrek SB

3
Kabul etmiyorum, ancak standardın neresinde std'ye uzmanlıklar eklemeye izin veriliyor ve teşvik ediliyoruz?
razeh

3
@Kerrek, katılıyorum ama standarttaki bir yere bir bölüm ve ayet referansı bekliyordum. Enjeksiyona izin veren ifadeyi 17.6.4.2.1'de "aksi belirtilmedikçe" izin verilmediğini söylediği yerde buldum, ancak 4000+ sayfa spesifikasyonu arasında "aksi belirtilmedikçe" bölümü bulamadım.
razeh

3
@razeh burada "Bir program, herhangi bir standart kitaplık şablonu için std ad alanına bir şablon uzmanlığı ekleyebilir ancak bildirim kullanıcı tanımlı bir türe bağlıysa ve uzmanlık orijinal şablon için standart kitaplık gereksinimlerini karşılıyorsa ve açıkça yasaklanmıyorsa . ". Yani bu çözüm tamam.
Marek R

7

Bahsim, unordered_map / unorder_set / ... sınıfları için Hash şablonu argümanı üzerinde olacaktır:

#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
}

Elbette

  • hashX, global bir statik işlev de olabilir
  • ikinci durumda, bunu geçebilirsin
    • eski moda functor nesnesi ( struct Xhasher { size_t operator(const X&) const; };)
    • std::hash<X>()
    • imzayı tatmin eden herhangi bir bağlama ifadesi -

Varsayılan bir kurucuya sahip olmayan bir şey yazabileceğinizi takdir ediyorum, ancak her bir harita yapısının ek argümanı hatırlamasını gerektirmenin biraz külfet olduğunu - benim zevkime göre oldukça fazla yük olduğunu düşünüyorum. Açık bir şablon argümanında sorun yok, ancak uzmanlaşmak std::hashhala en güzel çıkış yolu :-)
Kerrek SB

kurtarmaya kullanıcı tanımlı tipler :-) Daha ciddiyim, umarım onları sınıflarının a char*!
Kerrek SB

Hmm ... Bir hashuzmanlığın ADL yoluyla nasıl müdahale ettiğine dair bir örnek var mı? Demek istediğim, tamamen makul, ama bir sorun vakası bulmakta zorlanıyorum.
Kerrek SB


A'ya ihtiyacınız olana kadar hepsi eğlenceli ve oyunlar std::unordered_map<Whatever, Xunset>ve işe yaramıyor çünkü Xunsethasher türünüz varsayılan olarak oluşturulamaz.
Brian Gordon

4

@Kerrek SB, 1) ve 3) konularını ele aldı.

2) g ++ ve VC10 std::hash<T>::operator()farklı imzalarla bildirilse de, her iki kitaplık uygulaması da Standart uyumludur.

Standart, üyelerini belirtmez std::hash<T>. Sadece böyle bir uzmanlığın ikinci şablon argümanı için gerekli olan aynı "Hash" gereksinimlerini karşılaması gerektiğini söylüyor std::unordered_set. Yani:

  • Karma türü H, en az bir bağımsız değişken türüne sahip bir işlev nesnesidir Key.
  • H kopyalanabilir.
  • H yok edilebilir.
  • Eğer htipi bir ifadesidir Hya da const H, ve k(muhtemelen dönüştürülebilen bir tipte bir ifadesidir const) Key, daha sonra h(k)tip geçerli bir ifadedir size_t.
  • Eğer htipte bir ifadesidir Hya da const H, ve utipi bir lvalue olup Key, daha sonra h(u)tip geçerli bir ifadedir size_tdeğiştirmez u.

Hayır, her iki uygulama da bir bütün olarak std::hash<X>::operator()değil uzmanlaşmaya çalıştıkları için standart uyumlu değildir std::hash<X>ve imzası std::hash<T>::operator()uygulama tanımlıdır.
ildjarn

@ildjarn: Açıklığa kavuşturuldu - Denenen uzmanlıklardan değil, kütüphane uygulamalarından bahsediyordum. OP'nin tam olarak hangisini sormak istediğinden emin değilim.
aschepler
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.