【问题标题】:Specializing std::hash for derived classes works in gcc, not clang为派生类专门化 std::hash 在 gcc 中工作,而不是 clang
【发布时间】:2016-01-20 07:56:37
【问题描述】:

我正在尝试将 std::hash 专门用于衍生课程。迄今为止最好的方法是基于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;
}

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

这是编译器错误还是代码错误?

This question,提出了一个 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 中实现这一目标?

【问题讨论】:

  • 我认为template&lt;class T, class E&gt; first = T; 确实在扩展它,而将其专门用于E 显然不是一个好主意。您可以创建一个hash_base&lt;T&gt;,然后为可散列类型添加template&lt;&gt; struct hash&lt;your_type&gt; : hash_base&lt;your_type&gt; {};,而不是从hashable 派生您的类。
  • @BoPersson 我想避免为每种类型添加额外的行,尤其是因为它必须位于丑陋的命名空间中。
  • @Jarod42 这意味着,该标准甚至没有描述(当前)first-solution 应该如何编译,对吧?编辑:clang 似乎对待它不同,而 gcc 坚持使用 SF 的建议解决方案。
  • @Zulan 我想这不是重点。您的代码确实触发了替换失败,但是(成功时)别名模板等同于它指向的类型,因此您最终会得到 hash&lt;T&gt; 这不是专业化

标签: c++ gcc clang c++14 sfinae


【解决方案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;
}

【讨论】:

  • 当谈到咆哮时,我当然支持你。我希望能够保留完整的std 界面。当我引入 std 类型的替代品时,我总是有一种非常糟糕的感觉,因为它通常会带来惊喜。
  • boost::hash 不仅仅是 std::hash 的有效替代品。您所要做的就是在 X 的命名空间中定义 hash_value(const X&) 。 ADL 为您做所有其他事情。为什么 std 的委员会不遵循这个明显的逻辑路线对我来说是个谜。他们通常是一群聪明的家伙......而且 boost::range 也需要符合标准!哦哦。你让我开始了......
猜你喜欢
  • 1970-01-01
  • 2012-05-15
  • 1970-01-01
  • 2021-04-05
  • 2021-12-31
  • 1970-01-01
  • 2014-08-13
  • 2013-01-26
  • 2015-07-05
相关资源
最近更新 更多