下面的代码应该按照C ++标准编译吗?

时间:2016-12-14 13:10:55

标签: c++ templates gcc language-lawyer sfinae

#include <type_traits>

template <typename T>
struct C;

template<typename T1, typename T2>
using first = T1;

template <typename T>
struct C<first<T, std::enable_if_t<std::is_same<T, int>::value>>>
{
};

int main ()
{
}

不同编译器编译的结果:

MSVC:

  

错误C2753:&#39; C&#39;:部分特化与主模板的参数列表不匹配

GCC-4.9:

  

错误:部分专业化&#39; C&#39;不专门化任何模板参数

clang所有版本:

  

错误:类模板部分特化没有专门化任何模板参数;要定义主模板,请删除模板参数列表

GCC-5 +: 成功编译

另外我想指出那些琐碎的专业化:

template<typename T>
struct C<T>
{
};

成功地无法由gcc编译。所以它似乎认为我原来的例子中的专业化是非平凡的。所以我的问题是 - 这样的模式是否被C ++标准明确禁止?

2 个答案:

答案 0 :(得分:4)

关键段落是[temp.class.spec]/(8.2),这要求部分专业化比主要模板更专业化。 Clang实际上抱怨的是参数列表与主模板的相同:这已经被[temp.class.spec]/(8.3) issue 2033compiles the following中删除了(这表明需求是多余的),所以还没有实现在Clang尚未。但是,它显然已经在GCC中实现,因为它接受了你的代码段;它甚至1980,也许是因为它编译代码的原因相同(它也仅适用于版本5以后):

template <typename T>
void f( C<T> ) {}

template <typename T>
void f( C<first<T, std::enable_if_t<std::is_same<T, int>::value>>> ) {}

即。它承认声明是截然不同的,因此必须实施一些问题[temp.deduct.type]/1的解决方案。它没有发现第二个重载是更专业的(参见Wandbox链接),但这是不一致的,因为它应该根据(8.2)中的上述约束来诊断你的代码。

可以说,当前的措辞使你的例子的部分排序按照需要工作[temp.alias]/3提到从类型中扣除,

  

模板参数可以在几个不同的上下文中推导出来,但在每种情况下,根据模板参数(称为P)指定的类型与实际类型进行比较(称之为A ),并尝试查找模板参数值[...],在替换推导值后将P (称之为推导出的A),兼容与A

现在通过1157,这意味着在部分排序步骤中,部分特化的函数模板是参数模板,替换为is_same会产生错误(因为公共库实现只使用了部分特化必须失败),enable_if失败。但是这种语义在一般情况下并不令人满意,因为我们可以构造一个通常成功的条件,因此一个独特的合成类型会遇到它,并且演绎都是成功的。

据推测,最简单和最强大的解决方案是在部分排序期间忽略丢弃的参数(使您的示例格式错误)。在这种情况下,人们也可以将自己定位于实现的行为(类似于问题Clang):

template <typename...> struct C {};

template <typename T>
void f( C<T, int> ) = delete;

template <typename T>
void f( C<T, std::enable_if_t<sizeof(T) == sizeof(int), int>> ) {}

int main() {f<int>({});}

GCC1980都将此诊断为调用已删除的函数,即同意第一个重载比另一个更专业。 #2的关键属性似乎是第二个模板参数依赖,但T仅出现在非推导的上下文中(如果我们在#1中将int更改为T,则没有任何更改)。所以我们可以使用丢弃的(和依赖的?)模板参数作为纠结者:这样我们就不必推理合成值的本质,这是现状,并且在你的情况下也会得到合理的行为,这将是良好的形式。

@ T.C.提到通过[temp.class.order]生成的模板当前将被解释为一个多重声明的实体 - 请参阅问题1157。在这种情况下,这与标准没有直接关系,因为措辞从未提到这些函数模板被声明,更不用说在同一个程序中;它只是指定它们然后回到函数模板的过程。

执行此分析需要哪些深度实现并不完全清楚。问题enter image description here演示了“正确”确定模板的域是否是另一个域的正确子集所需的详细程度。实现部分排序是如此复杂,既不实际也不合理。然而,脚注部分只是表明这个主题未必具体说明,但有缺陷。

答案 1 :(得分:1)

我认为你可以简化你的代码 - 这与type_traits无关。您将获得与以下相同的结果:

template <typename T>
struct C;

template<typename T>
using first = T;

template <typename T>
struct C<first<T>>  // OK only in 5.1
{
};

int main ()
{
}

签入在线编译器(在5.1下编译但不在5.2或4.9下编译,因此它可能是一个错误) - https://godbolt.org/g/iVCbdm

我认为在GCC 5中他们转移了模板功能,甚至可以创建两个相同类型的特化。它会编译,直到你尝试使用它。

template <typename T>
struct C;

template<typename T1, typename T2>
using first = T1;

template<typename T1, typename T2>
using second = T2;

template <typename T>
struct C<first<T, T>>  // OK on 5.1+
{
};

template <typename T>
struct C<second<T, T>>  // OK on 5.1+
{
};

int main ()
{
   C<first<int, int>> dummy; // error: ambiguous template instantiation for 'struct C<int>'
}

https://godbolt.org/g/6oNGDP

它可能与添加对C ++ 14变量模板的支持有某种关系。 https://isocpp.org/files/papers/N3651.pdf