Как переопределить 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 явно, как в вашем примере.