Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to override std::hash for an enum defined inside a class?

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.

like image 822
Talin Avatar asked Sep 26 '15 17:09

Talin


3 Answers

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

like image 153
Nevermore Avatar answered Oct 23 '22 10:10

Nevermore


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;
  ...
like image 35
chris Avatar answered Oct 23 '22 08:10

chris


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.
like image 1
Lightness Races in Orbit Avatar answered Oct 23 '22 08:10

Lightness Races in Orbit