【问题标题】:Delete templated struct partial specialization删除模板化结构部分特化
【发布时间】:2018-07-07 09:38:57
【问题描述】:

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

template<typename T>
struct A {};

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

template<>
struct A<void> = delete;

这不会像预期的那样编译,因为这种语法不存在,尽管我本质上想要类似的东西。

虽然我目前对这个问题有一个“解决方案”,那就是删除所有可能的构造函数:

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 编写一个完美的包装器,也就是说,在 Helper 函数的帮助下,包装器的行为与原始类型完全相同

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。

这也适用于任何模板类,您只需将 A 替换为您尝试使用的类,并将 enable_if 替换为您的条件:

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

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

【问题讨论】:

  • 使用 static_assert。

标签: c++ templates template-specialization


【解决方案1】:

如果你真的想要一个“编译时”错误,你可以使用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&lt;void&gt; 没有正文。

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/ 做的事情

【讨论】:

  • 这就像我想要的那样工作,虽然我更喜欢专门 void 并放置一个只有 static_assert(false, "Now allowed") 的主体,以免弄乱原始结构,这有什么不同从把它放在结构的原始主体中?我编译了它,似乎没有,但有时这些东西可能存在细微差别。
  • 其实专精是不行的,我才发现我不小心弄错了,我以为专精没办法了?或者至少不必修改结构的原始主体?
  • @FilipeRodrigues 我理解您的担忧,请参阅我的更新。我在考虑,等一下
  • 更新:替代。那是我已经有的解决方案,它的问题是只要没有实例化对象,仍然可以使用该类型,例如在元函数中,但如果到目前为止还没有解决方案,我想我'只需要使用删除所有构造函数的方法,谢谢你的回答。我会将您标记为解决方案,因为静态断言方法适用于每个有权访问初始正文的人。
  • 啊,我找到了一种方法,只需声明模板 struct A;如果没有正文,当您尝试使用 A:“变量的类型不完整”时会出错。唯一的问题是不清楚错误的含义,其他人可以在之后自己声明它,不像删除,但我想这已经足够好了,你想把它添加到你的答案中吗?
【解决方案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

优点:

  • 不需要访问原始结构

缺点:

  • 只允许专注于一种类型
  • 用户可以在之后定义正文并删除此删除

3

您可以编写一个辅助类,它基本上将您想要专门化的类型包装成一个具有您无法控制的所有功能的新类型,因此您可以使用方法 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...>;

优点:

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

缺点:

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

【讨论】:

    猜你喜欢
    • 2011-10-30
    • 2012-11-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-16
    • 2014-06-13
    • 1970-01-01
    相关资源
    最近更新 更多