web-dev-qa-db-ja.com

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

_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;
_

タイプXdefaultハッシュを付けて_std::unordered_set<X>_だけを記述する方が便利です。コンパイラとライブラリ。相談後

  • C++標準 ドラフトN3242 §20.8.12[unord.hash]および§17.6.3.4[hash.requirements]、
  • Boost.Unordered
  • g ++ _include\c++\4.7.0\bits\functional_hash.h_
  • VC10 _include\xfunctional_
  • スタックオーバーフローのさまざまな 関連する質問 s

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. C++ 11標準に準拠しているstd::hash<X>::operator()バージョンはどれですか?

  3. ポータブルな方法はありますか?

95
René Richter

名前空間std *にspecializationsを追加することを明示的に許可および推奨します。ハッシュ関数を追加する正しい(そして基本的にのみの)方法はこれです:

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_toおよびstd::swap。)

*)関係するタイプの1つがユーザー定義である限り、私は思う。

119
Kerrek SB

私の賭けは、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>()
    • 署名を満たすバインド式-
6
sehe

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

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

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

  • ハッシュ型Hは、少なくとも1つの引数型Keyを持つ関数オブジェクトです。
  • Hは、コピー構築可能です。
  • Hは破壊可能です。
  • hHまたはconst H型の式であり、kが(おそらくconstKeyに変換可能な型の式である場合、h(k)は、タイプsize_tの有効な式です。
  • hHまたはconst H型の式であり、uKey型の左辺値である場合、h(u)は型を持つ有効な式ですuを変更しないsize_t
4
aschepler