c++ - 为派生类专门化 std::hash 在 gcc 中工作,而不是 clang

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

我正在尝试为派生类专门化 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 中实现这一点?

最佳答案

先吐槽一下:

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

关于c++ - 为派生类专门化 std::hash 在 gcc 中工作,而不是 clang,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33257292/

相关文章:

c++ - 有哪些数据库持久性设计模式?

c++ - 使用反转计数算法的段故障

r - Mac OS X R 错误 "ld: warning: directory not found for option"

c - "unrecognized escape sequence"windres 错误

c - 从 ARM 5 迁移到 ARM 6 编译器 : Use of undeclared identifier '__Vectors'

c++ - 括号检查功能只测试输入的前 2 个?

c++ - 值在常量表达式 C++ 中不可用

c - 了解简单 C 程序的汇编

使用不同编译器的 C++ 和可变参数

c++ - 带有 c++11 标志的 clang++ 显示右值引用无法绑定(bind)到左值错误