如何在无序容器中将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;

std::unordered_set<X> 使用type 的默认哈希值编写将更方便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)
维克多Lyuboslavsky

请注意,Google样式指南不鼓励std::hash(与std名称空间中的其他内容不同)进行特殊化;撒一粒盐。
富兰克林于

Answers:


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)。

*),我想,只要其中一种涉及类型是用户定义的即可。


3
虽然这是可能的,但您一般会建议这样做吗?我更喜欢实例化unorder_map<eltype, hash, equality>,以避免用有趣的ADL业务破坏某人的一天。(编辑 Pete Becker关于此主题的建议
sehe 2011年

2
@sehe:如果您周围有一个散列函子,则可以默认构造,但是为什么呢?(因为您只需实现member-,所以相等更容易operator==。)我的一般哲学是,如果该函数是自然的,并且本质上是唯一的“正确”函数(如词典对比较),则将其添加到中std。如果它是特殊的(例如无序对比较),那么我将其特定于容器类型。
Kerrek SB 2011年

3
我不同意,但是我们允许并鼓励在标准的哪儿添加专业到std?
razeh 2014年

3
@Kerrek,我同意,但是我希望能引用本章中某个章节的内容。我在17.6.4.2.1中找到了允许注入的措辞,其中说“除非另有说明”,否则是不允许的,但是在4000+页的规范中,我找不到“否则指定”的部分。
razeh 2014年

3
@razeh 在这里,您可以阅读 “只有在声明依赖于用户定义的类型且该特殊化满足原始模板的标准库要求且未被明确禁止的情况下,程序才可以向标准命名空间std添加任何标准库模板的模板特殊化。 ”。所以这个解决方案是可以的。
Marek R

7

我的赌注将放在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>()
    • 任何满足签名的绑定表达式-

我很欣赏您可以编写没有默认构造函数的东西,但是我总是发现,要求每个映射构造都记住附加参数有点麻烦-对我而言,这负担太多了。我std::hash
同意

用户定义的类型以进行救援:-)更严重的是,我希望我们已经在他们的课程包含char*!的阶段将它们拍在手腕上了!
Kerrek SB 2011年

嗯...您有一个关于hash专业化如何通过ADL进行干扰的示例吗?我的意思是,这完全合理,但是我很难提出问题案例。
Kerrek SB 2011年


直到您需要时std::unordered_map<Whatever, Xunset>,它都是很有趣的游戏,并且因为您的Xunset哈希器类型默认不是可构造的,所以它不起作用。
Brian Gordon

4

@Kerrek SB涵盖了1)和3)。

2)即使g ++和VC10声明std::hash<T>::operator()了不同的签名,两种库实现也符合标准。

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

  • 哈希类型H是一个函数对象,至少具有一个参数类型Key
  • H 是可复制构造的。
  • H 是可破坏的。
  • 如果h是类型H或的表达式const H,并且k是可转换为(可能const)的类型的表达式Key,则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 2011年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.