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

3

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.

Abingdon answered 26/9, 2015 at 17:38 Comment(2)
Enums can be forward declared, but I don't see how it helps.Repugnance
This has been fixed in c++14: https://mcmap.net/q/222688/-can-39-t-use-enum-class-as-unordered_map-keyMorice
A
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).

Athanor answered 26/9, 2015 at 19:48 Comment(0)
R
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;
  ...
Repugnance answered 26/9, 2015 at 17:55 Comment(1)
Ooh that's inventive. I wouldn't use it, but I did chuckle. :)Whey
W
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.
Whey answered 26/9, 2015 at 17:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.