順序付けされていないコンテナでユーザー定義型のstd :: hash <Key> :: operator()を特化する方法は?


101

で、ユーザー定義のキータイプをサポートするために、std::unordered_set<Key>そしてstd::unordered_map<Key, Value> 一つが提供しなければならないoperator==(Key, Key)とハッシュファンクタ:

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;

コンパイラとライブラリに付属する型の場合のように、typeのデフォルトのハッシュだけ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のコンパイラサポートがまだ実験的であるとすれば、私はClangを試していない---、これらは私の質問です。

  1. 名前空間にそのような特殊化を追加することは合法stdですか?私はそれについて複雑な気持ちを持っています。

  2. std::hash<X>::operator()C ++ 11標準に準拠しているバージョンがある場合、それはどれですか。

  3. それを行うためのポータブルな方法はありますか?


gcc 4.7.2で、グローバルを提供する必要がありましたoperator==(const Key, const Key)
Victor Lyuboslavsky 2013

std::hashstd名前空間の他のものとは異なり)の特殊化はGoogleスタイルガイドで推奨されていません。一粒の塩と一緒に服用してください。
フランクリンユー

回答:


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::lessstd::equal_tostd::swapです。)

*)関係する型の1つがユーザー定義である限り、私はそう思います。


3
これは可能ですが、一般的にこの方法で行うことをお勧めしますか?unorder_map<eltype, hash, equality>面白いADLビジネスで誰かの日を台無しにするのを避けるために、代わりにインスタンス化を好みます。(このトピックに関するピートベッカーのアドバイスを編集
sehe

2
@sehe:もしあなたがその周りにハッシュファンクタを置いているなら、それはおそらくデフォルトで構築可能ですが、なぜですか?(member-を実装するだけなので、平等は簡単ですoperator==。)私の一般的な哲学は、関数が自然で、本質的に「正しい」もの(辞書式ペアの比較など)であれば、それをに追加することstdです。それが(順序付けられていないペアの比較のように)特殊なものである場合は、コンテナタイプに固有のものにします。
Kerrek SB、2011

3
私は反対していませんが、標準でスペシャライゼーションをstdに追加することが許可および推奨されているのはどこですか?
razeh 14

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は、グローバル静的関数にすることもできます
  • 2番目のケースでは、それを渡すことができます
    • 昔ながらのファンクターオブジェクト(struct Xhasher { size_t operator(const X&) const; };
    • std::hash<X>()
    • 署名を満たすバインド式-

デフォルトのコンストラクタを持たないものを書いてもらえるとありがたいですが、追加の引数を覚えておくために各マップの構築を要求することは少し負担になります-私の好みにはかなり負担がかかりすぎます。私は明示的なテンプレート引数で大丈夫ですが、専門化std::hashはまだ最も良い方法です:-)
Kerrek SB

ユーザーが定義した型を救う:-)より真剣に、クラスにchar*!が含まれている段階ですでに手首にそれらをぶつけたいと思います。
Kerrek SB、2011

うーん...あなたはhash専門がADLを介してどのように干渉するかの例を持っていますか つまり、それは完全にもっともらしいことですが、問題のケースを見つけるのに苦労しています。
Kerrek SB、2011


あなたが必要とするまでstd::unordered_map<Whatever, Xunset>、それはすべて面白くてゲームであり、あなたのXunsetハッシャータイプはデフォルトで構築可能ではないので機能しません。
ブライアンゴードン

4

@Kerrek SBは1)と3)をカバーしています。

2)g ++とVC10 std::hash<T>::operator()は異なる署名で宣言しますが、両方のライブラリの実装は標準に準拠しています。

規格では、のメンバーを指定していませんstd::hash<T>。それは、そのような特殊化のそれぞれが、2番目のテンプレート引数などに必要な同じ「ハッシュ」要件を満たさなければならないことを示しstd::unordered_setています。すなわち:

  • ハッシュ型Hは、少なくとも1つの引数型を持つ関数オブジェクトKeyです。
  • H コピー構築可能です。
  • H 破壊可能です。
  • 場合は、h型の式であるH、またはconst H、及びk(恐らくはに変換型の式であるconstKey、次いで、h(k)型の有効な式ですsize_t
  • 場合h型の式であるHかはconst H、およびuタイプの左辺値であるKey場合、h(u)型の有効な式ですsize_t変更されませんu

いいえ、どちらの実装も標準に準拠していません。なぜなら、それらは全体としてではstd::hash<X>::operator()なく専門化しようとするものstd::hash<X>であり、のシグネチャstd::hash<T>::operator()は実装定義です。
ildjarn 2011年

@ildjarn:明確化-私はライブラリの実装について話していましたが、専門化の試みではありませんでした。OPが正確にどちらを要求するのかわからない。
aschepler
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.