Fed*_*dor 7 c++ templates language-lawyer c++20
考虑以下程序:
template<typename T>
struct A { T t[2]; };
int main()
{
A a{1}; // only one value provided for 2-element array
return a.t[0];
}
Run Code Online (Sandbox Code Playgroud)
在 C++20 模式下,它在gcc.
<source>(6): error C2641: cannot deduce template arguments for 'A'
<source>(6): error C2780: 'A<T> A(void)': expects 0 arguments - 1 provided
<source>(2): note: see declaration of 'A'
<source>(6): error C2784: 'A<T> A(A<T>)': could not deduce template argument for 'A<T>' from 'int'
<source>(2): note: see declaration of 'A'
<source>(6): error C2784: 'A<T> A(A<T>)': could not deduce template argument for 'A<T>' from 'int'
<source>(2): note: see declaration of 'A'
<source>(6): error C2780: 'A<T> A(T,T)': expects 2 arguments - 1 provided
<source>(2): note: see declaration of 'A'
Run Code Online (Sandbox Code Playgroud)
https://gcc.godbolt.org/z/6ejfW8G77
这两个编译器中的哪一个是正确的?
(Clang这里是没有问题的,因为它还不支持带括号的聚合初始化)。
更新:微软承认了这个错误并承诺很快修复:https : //developercommunity.visualstudio.com/t/template-argument-deduction-fails-in-case-of-aggre/1467260
GCC 是对的,代码应该被接受,并且 的类型a应该被推导为A<int>使用类模板参数推导(CTAD)进行聚合。
N4868(最接近已发布的 C++20 标准)[over.match.class.deduct]/1说(我排除了一些与此处不相关的部分):
当解析推导类类型的占位符时,其中 模板名称命名主类模板
C,形成一组函数和函数模板,称为 的指南C,包括:
[...]
此外, ifC被定义及其定义满足聚合类的条件,假设任何依赖基类没有虚函数和虚基类,并且初始化器是非空的花括号初始化列表或括号表达式列表,并且没有推导-guide s forC,该集合包含一个附加函数模板,称为 聚合推导候选,定义如下。令x 1 ,..., x n为花括号初始化列表或表达式列表的初始化列表或指定初始化列表的元素。对于每个x i,令e i为其(可能是递归的)子聚合之一的对应聚合元素,该子聚合将由x i初始化,如果C
- 对于具有依赖非数组类型或具有值依赖边界的数组类型的任何聚合元素,不考虑大括号省略,
[...]如果对于任何xi都不存在这样的聚合元素e i ,则聚合推导候选不会添加到集合中。候选聚合推论是从假设的构造函数中导出的,其中
C(T1,...,Tn)
- [...]
- 否则,是e i
Ti的声明类型,[...]
的实例化A是聚合。只有x 1,1. 关于不考虑大括号省略的要点并不适用,因为我们有一个子聚合 ( T t[2]),它是一个具有依赖类型但不具有值依赖边界的数组。因此考虑大括号省略,并且将1初始化t[0],即e 1,并且类型为T。
因此,总计推导候选为
template<typename T> A<T> F(T)
Run Code Online (Sandbox Code Playgroud)
可以用来推导A<int>.
MSVC 无法生成此模板。错误消息表明它错误地生成了具有两个函数参数的函数。
聚合的 CTAD 在P1816中指定,但允许其工作的规则出现在另一篇论文 P2082中,该论文解决了几个问题。MSVC声称从 VS 2019 16.7 开始实现这两个功能,但从 16.10.3 开始仍然拒绝该代码。
作为附加数据点,严格 C++20 模式下的 EDG 6.2 接受该代码。