Специализация std::hash для производных классов работает в gcc, а не в clang

Я пытаюсь специализировать std::hash для производных классов. Пока лучший подход основан на этом ответе:

#include <type_traits>
#include <functional>
#include <unordered_set>

namespace foo
{
    template<class T, class E>
    using first = T;

    struct hashable {};
    struct bar : public hashable {};
}

namespace std
{
    template <typename T>
    struct hash<foo::first<T, std::enable_if_t<std::is_base_of<foo::hashable, T>::value>>>
    {
        size_t operator()(const T& x) const { return 13; }
    };
}

int main() {
    std::unordered_set<foo::bar> baz;
    return 0;
}

Это компилируется с g++ 5.2.0 без предупреждений (-Wall -pedantic), но с clang++ 3.7.0 приводит к следующей ошибке:

first.cpp:17:12: error: class template partial specialization does not specialize any template argument; to define the primary template, remove the template argument list
    struct hash<foo::first<T, std::enable_if_t<std::is_base_of<foo::hashable, T>::value>>>
           ^   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Это ошибка компилятора или ошибка в коде?

Этот вопрос предлагает решение SFINAE, которое технически работает как с моими версиями gcc, так и с clang. Однако, поскольку он отключил только оператор, а не класс, он начинает выдавать очень запутанные сообщения об ошибках, когда кто-то пытается хэшировать любой нехешируемый класс:

template <typename T>
struct hash
{
    typename std::enable_if_t<std::is_base_of<foo::hashable, T>::value, std::size_t>
    operator()(const T& x) const { return 13; }
};
...
struct fail {};
std::unordered_set<fail> bay;
...
type_traits:2388:44: error: no type named 'type' in 'std::enable_if<false, unsigned long>';
  'enable_if' cannot be used to disable this declaration

Я бы не хотел рассматривать макрорешение. Далее я пробовал следующие подходы:

template <typename T>
struct hash<std::enable_if_t<std::is_base_of<foo::hashable, T>::value, T>>

Оба компилятора жалуются, что не могут вывести тип, что меня раздражает, потому что я не вижу большой разницы с решением first.

Моя первая попытка была обычным шаблоном для enable_if:

template <typename T,
          typename DUMMY = std::enable_if_t<std::is_base_of<foo::hashable, T>::value>>
struct hash<T>

Что не удается с аргументом шаблона по умолчанию в частичной специализации шаблона класса.

Есть ли чистый способ метапрограммирования шаблонов для достижения этого в C++14?


person Zulan    schedule 21.10.2015    source источник
comment
Я думаю, что template<class T, class E> first = T; действительно преувеличивает, и специализироваться на E, очевидно, не лучшая идея. Вместо того, чтобы наследовать классы от hashable, вы можете создать hash_base<T>, а затем добавить template<> struct hash<your_type> : hash_base<your_type> {}; для хэшируемых типов.   -  person Bo Persson    schedule 21.10.2015
comment
Интересная ссылка: open-std.org/jtc1/sc22 /wg21/docs/cwg_defects.html#1558   -  person Jarod42    schedule 21.10.2015
comment
@BoPersson Я хотел избежать дополнительной строки для каждого типа, тем более что она должна была быть в уродливом пространстве имен.   -  person Zulan    schedule 21.10.2015
comment
@ Jarod42 Jarod42 Это означает, что стандарт даже не описывает (в настоящее время), как должно компилироваться first-решение, верно? Редактировать: И clang, похоже, относится к этому по-разному, в то время как gcc придерживается предложенного решения по использованию SF.   -  person Zulan    schedule 21.10.2015
comment
@Zulan Я думаю, дело не в этом. Ваш код вызывает ошибку замены, но (в случае успеха) шаблон псевдонима эквивалентен типу, на который он указывает, поэтому вы получаете hash<T>, который не является специализацией.   -  person Piotr Skotnicki    schedule 21.10.2015


Ответы (1)


сначала небольшой бред:

дизайн std::hash ужасен. Частичная специализация не допускается. комитет должен был просто полностью скопировать реализацию boost.

(разглагольствовать)

Я думаю, что одним элегантным решением является подход к этому с другой стороны:

#include <type_traits>
#include <functional>
#include <unordered_set>

namespace foo
{
    template<class T, class E>
    using first = T;

    struct hashable {};
    struct bar : public hashable {};

    template<class T, typename = void>
    struct hashable_hasher;

    template<class T>
    struct hashable_hasher<T, std::enable_if_t<std::is_base_of<hashable, T>::value>>
    {
        size_t operator()(const T& x) const { return 13; }
    };


    template<class T, typename = void>
    struct choose_hash {
        using type = std::hash<T>;
    };

    template<class T>
    struct choose_hash<T, std::enable_if_t<std::is_base_of<hashable, T>::value>> {
        using type = hashable_hasher<T>;
    };

    template<class T>
    using choose_hash_t = typename choose_hash<T>::type;

    template<class T>
    using choose_set_t = std::unordered_set<T, choose_hash_t<T>>;
}

int main() {
    foo::choose_set_t<foo::bar> baz;
    return 0;
}
person Richard Hodges    schedule 21.10.2015
comment
Я, конечно, с вами, когда дело доходит до разглагольствования. Я надеялся сохранить полный интерфейс std. У меня всегда очень плохое предчувствие, когда я представляю замену для типа std, потому что обычно это приводит к неожиданностям. - person Zulan; 21.10.2015
comment
boost::hash — более чем достойная замена std::hash. Все, что вам нужно сделать, это определить hash_value(const X&) в пространстве имен X. ADL сделает все остальное за вас. Почему комитет STD не последовал этой очевидной логике, для меня загадка. Обычно это отличные ребята... и boost::range тоже должен быть в стандарте! ой ой. ты меня завел... - person Richard Hodges; 21.10.2015