Как переопределить std:: hash для перечисления, определенного внутри класса?
У меня есть тип перечисления, определенный внутри класса, и я хочу создать 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>
Однако мне интересно, есть ли способ специализировать std:: hash для перечисления Bar, чтобы каждый, кто использует unordered_map, автоматически получает поведение хэширования.
Это работает с любым другим типом данных, но не перечисляет - потому что перечисления не могут быть объявлены вперед.
Чтобы это сработало, мне пришлось бы поместить определение std:: hash после определения перечисления, но до первого использования, что означает, что мне пришлось бы поставить его в середине тела класса, который не будет работать.
Ответы
Ответ 1
Однако мне интересно, есть ли способ специализировать std:: hash для перечисления Bar, чтобы каждый, кто использует unordered_map, автоматически получает поведение хэширования.
Нет чудес, поэтому каждый будет использовать специализированный std::hash
только после его специализации. Поскольку вы не можете специализировать классы внутри другого класса, и ваше перечисление вложенно, будет проблематично использовать std::hash
внутри класса. Как вы указали, перечисления не могут быть объявлены вперед. Таким образом, существует единственное решение (без создания базовых классов или "ненужных" перечислений), чтобы использовать специализированный std::hash
внутри класса: aggregate/declare by reference и использовать внешнюю службу после 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);
}
Распечатывается
хэш < A:: E > :: operator()
хэш-л; A:: E > :: operator()
Таким образом, вне класса A
каждый может использовать A::E
с std::hash
, а также внутри класса, мы также используем A::E
с std::hash
. Кроме того, если вы не хотите агрегировать std::unordered_set
по ссылке, вы можете реализовать пользовательский хешер только для внутреннего использования (а затем перенаправить std::hash
на него).
Ответ 2
Одна возможность - перевести enum в базовый класс. К сожалению, вы должны предоставить декларацию использования для каждого члена перечисления. Один из способов - использовать область enum (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;
...
Ответ 3
Вы, кажется, уже покрыли все углы вашего вопроса.
Я не могу придумать, как это сделать.
Напомним, вы можете только изменить факты ситуации:
- сделайте
enum
не вложенным (вместо этого поставьте его в закрывающееся пространство имен) или
- используйте функцию hasher явно, как в вашем примере.