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.
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).
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;
...
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:
enum
non-nested (put it in an enclosing namespace instead), orIf you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With