Почему этот код обеспечивает специализацию для перечислений **ALL** для шаблона std::hash?

Я не профессионал в C++, но каким-то образом я предоставил решение, портируя мой код MSVS 2015 C++ на MinGW 4.9.2, чтобы специализироваться std::hash класс, чтобы поддержать всех enums. Если здесь есть разработчики компиляторов C++ или программисты C++ pro, можете ли вы объяснить, почему эта специализация работает, хотя она и называется Undefined Behavior в соответствии со стандартом C++, говорят они?

Ссылка на полный код с примерами на Gist

#include <unordered_set>
#include <functional>

#if (defined __GNUC__) && (__GNUC__ < 6)
// Adds support of std::hash<enum T> to libstdc++.
// GCC 6 provides it out of the box.
namespace std {
    template<typename T>
    struct hash {
        constexpr size_t operator()(typename std::enable_if<std::is_enum<T>::value, T>::type s) const noexcept {
            return static_cast<size_t>(s);
        }
    };
}
#endif

Оказание поддержки std::hash<enum T> означает, что все классы, как std::unordered_XXX поддержит любой enum в качестве ключа.

GCC 6.1.0 с этим std::hash определение не удается с ошибкой

error: redefinition of 'struct std::hash<_Tp>'

GCC 5.3.0 без этого std::hash определение не удалось для std::unordered_set со следующей ошибкой:

In file included from /usr/local/gcc-5.3.0/include/c++/5.3.0/bits/hashtable.h:35:0,
                 from /usr/local/gcc-5.3.0/include/c++/5.3.0/unordered_set:47,
                 from prog.cc:1:
/usr/local/gcc-5.3.0/include/c++/5.3.0/bits/hashtable_policy.h: In instantiation of 'struct std::__detail::__is_noexcept_hash<Foo, std::hash<Foo> >':
/usr/local/gcc-5.3.0/include/c++/5.3.0/type_traits:137:12:   required from 'struct std::__and_<std::__is_fast_hash<std::hash<Foo> >, std::__detail::__is_noexcept_hash<Foo, std::hash<Foo> > >'
/usr/local/gcc-5.3.0/include/c++/5.3.0/type_traits:148:38:   required from 'struct std::__not_<std::__and_<std::__is_fast_hash<std::hash<Foo> >, std::__detail::__is_noexcept_hash<Foo, std::hash<Foo> > > >'
/usr/local/gcc-5.3.0/include/c++/5.3.0/bits/unordered_set.h:95:63:   required from 'class std::unordered_set<Foo>'
prog.cc:25:25:   required from here
/usr/local/gcc-5.3.0/include/c++/5.3.0/bits/hashtable_policy.h:85:34: error: no match for call to '(const std::hash<Foo>) (const Foo&)'
  noexcept(declval<const _Hash&>()(declval<const _Key&>()))>
                                  ^
In file included from /usr/local/gcc-5.3.0/include/c++/5.3.0/bits/move.h:57:0,
                 from /usr/local/gcc-5.3.0/include/c++/5.3.0/bits/stl_pair.h:59,
                 from /usr/local/gcc-5.3.0/include/c++/5.3.0/utility:70,
                 from /usr/local/gcc-5.3.0/include/c++/5.3.0/unordered_set:38,
                 from prog.cc:1:
/usr/local/gcc-5.3.0/include/c++/5.3.0/type_traits: In instantiation of 'struct std::__not_<std::__and_<std::__is_fast_hash<std::hash<Foo> >, std::__detail::__is_noexcept_hash<Foo, std::hash<Foo> > > >':
/usr/local/gcc-5.3.0/include/c++/5.3.0/bits/unordered_set.h:95:63:   required from 'class std::unordered_set<Foo>'
prog.cc:25:25:   required from here
/usr/local/gcc-5.3.0/include/c++/5.3.0/type_traits:148:38: error: 'value' is not a member of 'std::__and_<std::__is_fast_hash<std::hash<Foo> >, std::__detail::__is_noexcept_hash<Foo, std::hash<Foo> > >'
     : public integral_constant<bool, !_Pp::value>
...

1 ответ

Решение

Это плохо сформированная программа, для которой не требуется диагностика, поскольку не разрешено предоставлять базовые специализации для типов шаблонов в std,

Это сработало, потому что в некоторых реализациях такая базовая специализация перенаправляет все std::hash<T> без явной специализации к вашей хэш-реализации. Тогда ваш operator() приступает только к работе с enums, но это не то, почему это работает, и при этом это не препятствует тому, чтобы ваша программа была плохо сформирована без какой-либо диагностики.

Практически, это, вероятно, сломалось, потому что кто-то включил SFINAE std::hash с пустой базовой реализацией в gcc 6.1: это может быть предписано некоторым стандартом C++, неуверенно, но это улучшение качества реализации, если нет.

Правильный способ сделать это - создать

struct enum_hash {
  template<typename T>
  constexpr
  typename std::enable_if<std::is_enum<T>::value,std::size_t>::type
  operator()(T s) const noexcept {
    return static_cast<std::size_t>(s);
  }
};

Это тип, который может хешировать любое перечисление.

Теперь пройдите ,enum_hash в unordered_set<some_enum, enum_hash> как это.

Другие вопросы по тегам