Specializing std::hash for derived classes works in gcc, not clang

六月ゝ 毕业季﹏ 提交于 2019-12-10 15:18:11

问题


I am trying to specialize std::hash for derved classes. The best approach so far is based on this answer:

#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;
}

This compiles with g++ 5.2.0 with no warnings (-Wall -pedantic), but with clang++ 3.7.0 it results in the following error:

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>>>
           ^   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Is this a compiler error or an error in the code?

This question, proposes a SFINAE solution that technically works with both my gcc and clang versions. However, because it only disabled the operator, not the class, It starts to yield very confusing error messages when one tries to hash any non-hashable class:

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

I would like to not consider the macro solution. I have further tried the following approaches:

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

Both compiler complain that they cannot deduce the type, which I find rather irritating because I don't see much of a difference towards the first solution.

My first attempt was the usual common pattern for enable_if:

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

Which fails with default template argument in a class template partial specialization.

Is there a clean template metaprogramming way to acchieve this in C++14?


回答1:


first a little rant:

the design of std::hash is awful. Partial specialisations are not allowed. the committee should have simply copied the boost implementation in full.

(rant over)

I think one elegant solution is to approach it from a different angle:

#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;
}


来源:https://stackoverflow.com/questions/33257292/specializing-stdhash-for-derived-classes-works-in-gcc-not-clang

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!