删除模板化结构偏特化

Delete templated struct partial specialization

如果我有这样的模板化结构:

template<typename T>
struct A {};

如何删除特定类型(例如 void)的部分特化,以便任何提及 A 都会产生编译器错误?

template<>
struct A<void> = delete;

这没有像预期的那样编译,因为这个语法不存在,虽然我本质上想要类似的东西。

虽然我目前对这个问题有一个'solution',就是删除所有可能的构造函数:

template<>
struct A<void> { template<typename...> A(...) = delete; };

但这不是最佳解决方案,因为用户仍然可以使用 A,只要他们不尝试实例化对象即可。我还尝试将 enable_if 添加到初始结构中,如下所示:

template<typename T, typename = typename std::enable_if<!std::is_same<T, void>::value>::type>
struct A {};

这很好用,所以提到 A 会产生编译器错误(虽然有点奇怪,因为它指的是 enable_if 本身),但是如果我无法访问初始定义,例如在专门化其他人的结构时。

那么除了我目前的解决方案之外,还有什么好的方法可以做到这一点,如果没有,有没有办法改进当用户使用 A 时我必须给出编译器错误的方法?

我可以使用 c++17 和 clang 的 c++2a,如果它添加了任何有助于此的新功能。


更新:正如@PicaudVincent 在他的回答中所说,第二种解决方案不允许您根据条件限制所有类型,只能限制单一类型,但有一种解决方法:

我们可以围绕我们的结构 A 编写一个完美的包装器,也就是说,一个在辅助函数的帮助下表现得与原始类型完全一样的包装器

template<template<typename...> class U, typename=void, typename...TArgs>
struct Helper : U<TArgs...>
{
    using U<TArgs...>::U;

    template<typename...Args>
    Helper(Args...args) : U<TArgs...>(args...) {};
};

template<typename...Args>
using B = Helper<A,
                 typename std::enable_if<(... && std::is_arithmetic<Args>::value)>::type, 
                 Args...>;

现在您可以使用 B<> 而不是 A<>,即使它们是同一事物并且会以相同的方式运行并且会在需要时转换为 A<>,例如在调用函数时接受 A<>.

尽管如果一个函数接受 B<>,则您不能传递 A<>,除非您使用 static_cast 或类似的方法将 A<> 显式转换为 B<>。

这也适用于任何模板化的 class,您只需用 A 代替您尝试使用的 class 和 enable_if 以符合您的条件:

using B = Helper</*Structure to use*/,
                 typename std::enable_if</*Condition*/>::type, 
                 Args...>;

这不是最好的解决方案,但它确实允许您现在必须更改原始声明并仍然限制您可以放入其中的内容,而无需手动命名每种类型。

如果你真的想要一个 "compile-time" 错误,你可以使用 static_assert。用--std=c++14编译的代码如下:

#include <type_traits>

template <typename T>
struct A
{
  static_assert(!std::is_same<T, void>::value, "Not allowed");
};


int main()
{
  A<double> a_d;
  A<void> a_v;     // <- compile time error
}

更新:

我理解你的评论。不幸的是:

template <>
struct A<void>
{
  static_assert(false, "Not allowed");
};

不起作用,因为肯定条件为假并且编译器检测到。

不确定在这种情况下我们能否找到基于 static_assert 的解决方案。我这边暂时没有找到


更新 2:Filipe Rodrigues 自答

或者,简单地定义 struct A<void> 没有正文。

template<> struct A<void>; 

然后代码如下:

int main()
{
  A<double> a_d;
  A<void> a_v;
}

触发这种错误:

aggregate ‘A<void> a_v’ has incomplete type and cannot be defined

1/对2/

2/ 的优点是简单,但是用 1/ 你可以这样写:

template <typename T>
struct A
{
  static_assert(std::is_arithmetic<T>::value, 
                "A<T>, T: must be an arithmetic type");
};

这是 2/

做不到的

最后,有 3 种方法可以实现这一点,每种方法都有优缺点:

1。 [@PicaudVincent 的建议]

在结构的初始主体中,写一个static_assert,条件

template<typename T>
struct A
{
    static_assert(!std::is_same<T, void>::value);
    ...
}

优点:

  • 轻松添加新条件
  • 可以添加任何条件
  • 生成了有用的错误消息

缺点:

  • 需要访问原始结构

2.

在没有主体的情况下定义特化,从而防止编译器能够使用该特化

template<>
struct A<void>;

尝试实例化对象时会给出这样的错误消息:

aggregate 'A<void> Var' has incomplete type and cannot be defined

优点:

  • 不需要访问原始结构

缺点:

  • 只允许专攻一种类型
  • 用户可以事后定义一个body,删除这个删除

3

您可以编写一个助手 class,它基本上将您想要专门化的类型包装成一个新类型,具有您无法控制的所有功能,因此您可以使用方法 1 对其进行自定义,甚至只是 std::enable_if.

template<template<typename...> class U, typename=void, typename...TArgs>
struct Helper : U<TArgs...>
{
    using U<TArgs...>::U;
    
    template<typename...Args>
    Helper(Args...args) : U<TArgs...>(args...) {};
    
    static_assert(/*Condition*/);
};

template<typename...Args>
using B = Helper</*Type to wrap around*/,
                 typename std::enable_if</*Condition*/>::type, 
                 Args...>;

优点:

  • 不需要定义主体并允许对 class
  • 进行更多控制
  • 轻松添加新条件
  • 可以添加任何条件
  • 有用的错误消息 static_assert
  • 类型 B 的工作原理与 A 相同,因为它可以轻松转换

缺点:

  • 包装类型,因此您必须将 A 的所有声明切换为 B
  • 接受 B 的函数不能接受 A,所以大多数函数应该用 A 声明,这意味着跟踪 2 种类型而不是 1 种(除非使用纯粹的 B)。