如何在无序容器中为用户定义的类型专门化std :: hash <key> :: operator()?</key>

时间:2011-11-16 20:05:42

标签: c++ hash c++11 unordered-map unordered-set

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;

仅编写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. 有可行的方法吗?

3 个答案:

答案 0 :(得分:115)

明确允许并鼓励您将特化添加到命名空间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 :(得分:6)

我打赌将在unordered_map / unorder_set / ...类的Hash模板参数上:

#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也可以是全局静态函数
  • 在第二种情况下,你可以通过
    • 古老的仿函数对象(struct Xhasher { size_t operator(const X&) const; };
    • std::hash<X>()
    • 满足签名的任何绑定表达式 -

答案 2 :(得分:4)

@Kerrek SB已经涵盖1)和3)。

2)尽管g ++和VC10使用不同的签名声明std::hash<T>::operator(),但两个库实现都符合标准。

标准未指定std::hash<T>的成员。它只是说每个这样的专业化必须满足std::unordered_set的第二个模板参数所需的相同“哈希”要求,依此类推。即:

  • 哈希类型H是一个函数对象,至少有一个参数类型Key
  • H是可复制的。
  • H可以破坏。
  • 如果hHconst H类型的表达式,k是可转换为(可能const){{{ 1}},然后Key是类型为h(k)的有效表达式。
  • 如果size_thH类型的表达式,而const Hu类型的左值,那么Key是类型为h(u)的有效表达式,不会修改size_t