11

I've got an enumeration type defined inside of a class, and I want to create an unordered_set of those objects as a member of the class:

#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;
};

Now, I know the obvious answer is to add a custom hash function to the unordered_set:

std::unordered_set<Bar, BarHasher>

However, what I'm wondering is if there's a way to specialize std::hash for the Bar enum so that anyone who uses unordered_map gets the hashing behavior automatically.

This works with every other data type, but not enums - because enums cannot be forward declared.

In order for this to work, I'd have to put the definition of std::hash after the enum definition, but before the first use, which means I'd have to put it in the middle of the class body, which won't work.

Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
Talin
  • 1,397
  • 2
  • 15
  • 30

3 Answers3

4

However, what I'm wondering is if there's a way to specialize std::hash for the Bar enum so that anyone who uses unordered_map gets the hashing behavior automatically.

There are no miracles, so anyone will use specialized std::hash only after its specialization. Since you cannot specialize classes inside another class and your enum is nested it will be problematically to use std::hash inside the class. As you pointed enums cannot be forward declared. So there is the only solution (without creating base classes or "unnesting" enums) to use specialized std::hash inside the class: aggregate / declare by reference and use outside after std::hash specialization.

#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);
}

Prints out

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

So, outside the class A everyone can use A::E with a std::hash as well as inside class we also use A::E with std::hash. Also, if you don't want to aggregate std::unordered_set by reference you may implement custom hasher for internal usage only (and then forward std::hash calls to it).

Nevermore
  • 1,127
  • 9
  • 12
3

One possibility is to put the enum into a base class. Unfortunately, you have to provide a using declaration for each enum member. One way around that is to use a scoped enum (enum class Bar), which requires use like Foo::Bar::SOME_VALUE instead of Foo::SOME_VALUE. Doing this, you'd need just the 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;
  ...
chris
  • 60,560
  • 13
  • 143
  • 205
1

You seem to have covered all the angles already in your question.

I can't think of a way to do this.

To recap, you can only change the facts of the situation:

  • make the enum non-nested (put it in an enclosing namespace instead), or
  • use the hasher function explicitly as in your example.
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055