删除模板化的结构部分专业化

时间:2018-07-07 09:38:57

标签: c++ templates template-specialization

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

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 会导致编译器错误(尽管有些cr讽,因为它指的是enable_if本身),但是如果我无法访问,这是行不通的最初的定义,例如当专门化别人的结构时。

因此,除了我到目前为止提供的解决方案之外,还有什么好方法可以做到这一点;如果没有,是否可以改善当用户使用A ?我必须给出编译器错误的方法? / p>

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


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

我们可以在struct 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 <>,则除非通过static_cast或类似方法将A <>明确转换为B <>,否则无法传递A <>。

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

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

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

2 个答案:

答案 0 :(得分:2)

如果您确实想要“编译时”错误,则可以使用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");
};

不起作用,因为确定为false的条件并且编译器检测到了。

不确定在那种情况下我们能否找到基于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 /

答案 1 :(得分:1)

最后,有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)。