如何为类内定义的枚举类型重载std::hash?

11

我在一个类中定义了一个枚举类型,我想创建一个unordered_set作为该类的成员来存储这些对象:

#include <unordered_set>

class Foo {
public:
  enum Bar {
    SOME_VALUE
  };

  // Error: implicit instantiation of std::hash
  std::unordered_set<Bar> getValues() const {
     return _values;
  }

private:
  std::unordered_set<Bar> _values;
};

现在,我知道显而易见的答案是向unordered_set添加一个自定义哈希函数:

std::unordered_set<Bar, BarHasher>

然而,我想知道是否有一种方法可以专门为Bar枚举类型设置std::hash,以便任何使用unordered_map的人都可以自动获得哈希行为。

这对于每种其他数据类型都有效,但不适用于枚举类型 - 因为枚举类型无法进行前向声明。

为了使此方法起作用,我必须将std::hash的定义放在枚举定义之后,但在第一次使用之前,这意味着我必须将其放在类体的中间位置,但这是行不通的。


枚举类型可以进行前向声明,但我不知道它有什么帮助。 - chris
1
这个问题已经在C++14中得到了修复:https://dev59.com/mGMk5IYBdhLWcg3w8SPa#29618545 - Vaughn Cato
3个回答

4
然而,我的疑惑是是否有一种方法可以专门为Bar枚举特化std :: hash,以便任何使用unordered_map的人都可以自动获得哈希行为。
没有什么奇迹,因此在其专业化之后,任何人都将使用专门的std :: hash。由于您无法在另一个类中专门化类并且您的枚举是嵌套的,因此在类内部使用std :: hash将会有问题。正如您所指出的,枚举不能被前向声明。因此,唯一的解决方案(不创建基类或“取消嵌套”枚举)是在类内部使用专门的std :: hash:聚合/通过引用声明,并在std :: hash专业化后在外部使用。
#include <iostream>
#include <unordered_set>
#include <memory>

struct A {

    enum E {
        first, second
    };

    A();

    std::unique_ptr< std::unordered_set<E> > s_; //!< Here is
};

namespace std {

template<>
class hash<A::E> {
public:
    std::size_t operator()(A::E const& key) const noexcept {
        std::cout << "hash< A::E >::operator()" << std::endl;
        return key;
    }

};

}

A::A()
    : s_(new std::unordered_set<E>)
{ }

int main(void) {
    A a;
    a.s_->insert(A::first);

    std::unordered_set< A::E > s;
    s.insert(A::second);
}

打印输出

hash< A::E >::operator()
hash< A::E >::operator()

因此,在类A外部,每个人都可以使用带有std::hashA::E,在类内部我们也使用A::Estd::hash。 同样,如果您不想通过引用聚合std::unordered_set,则可以仅为内部使用实现自定义哈希器(然后将std::hash调用转发到它)。


3

一种可能的解决方案是将枚举放入一个基类中。不幸的是,你必须为每个枚举成员提供一个使用声明。解决这个问题的方法是使用作用域枚举(enum class Bar),它需要像Foo::Bar::SOME_VALUE这样的用法,而不是Foo::SOME_VALUE。这样做,你只需要using FooBase::Bar;

class FooBase {
public:
  enum Bar {
    SOME_VALUE
  };

protected:
  ~FooBase() = default; //so can't be used polymorphically
};

//hash goes here

class Foo : FooBase {
public:
  using FooBase::Bar;
  using FooBase::SOME_VALUE;
  ...

哦,那很有创意。我不会用它,但我笑了。 :) - Lightness Races in Orbit

1

你的问题已经覆盖了所有方面。

我想不出一种方法来做到这一点。

总之,你只能改变情况的事实:

  • enum非嵌套(而是放在一个封闭的命名空间中),或者
  • 像你的示例中那样显式地使用哈希函数。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接