为什么在聚合类型推导中不支持花括号初始化列表,但支持花括号省略?

25
为什么在聚合类型推导中不支持花括号初始化列表,但支持花括号省略?
#include <iostream>
template<typename T>
struct Test{
    T t[2];
};
int main(){
  Test t{{1,2}};  // #1
  // Test t1{1,2} // #2
}

#1被GCCrejected,而#2会被GCC接受。

根据over.match.class.deduct#1

此外,如果定义了C并且它的定义满足聚合类的条件([dcl.init.aggr]),假设任何相关基类都没有虚函数和虚基类,并且初始化程序是非空的大括号初始化列表或带括号的表达式列表,并且C没有模板参数推导,则集合包含一个额外的函数模板,称为聚合推导候选项,其定义如下。设X1,...,XN是大括号初始化列表或表达式列表的元素。对于每个Xi,让ei是与C或其(可能递归)子聚合中的一个将被Xi初始化的相应聚合元素,如果

  • [1.5] 未考虑任何具有依赖性非数组类型或带有值依赖性绑定的数组类型的聚合元素的大括号省略,以及
  • [1.6] 每个不在末尾的聚合扩展的聚合元素被假定不对初始化列表的任何元素对应,以及
  • [1.7] 作为扩展包的末尾聚合元素被假定对应于初始化列表的所有剩余元素(如果有)。

如果没有任何Xi的这种聚合元素ei,则不会将聚合推导候选项添加到集合中。该聚合推导候选项是从假想构造函数C(T1,...,Tn)中派生出来的,如上所述

  • 如果ei是数组类型且xi是大括号初始化列表或字符串文字,则Ti是对ei的声明类型的右值引用

在我的例子中,x1是一个大括号初始化列表({1,2}),而e1的类型是数组类型T[2],因此构造函数应该是形式为C(T(&&)[2]),并且可以从temp.deduct.call#1中根据{1,2}推导出T(&&)[2]的模板参数。

为什么上面的例子被GCC拒绝了?然而,GCC接受大括号省略方式?如何解释这个例子?这是否被认为是GCC的错误或者是我误解了什么?
另一个我认为很奇怪的问题是,如果Xi是用于初始化子聚合的大括号初始化列表,如果bullet [1.5]成立,则Xi将用于初始化子聚合的元素。这是什么意思?
更新
在进一步研究p2082r1之后,从其上下文来看,“聚合元素”一词似乎是指聚合类型的元素而不是聚合的元素。如果满足bullet [1.5]、[1.6]、[1.7],则ei将成为聚合元素。然而,如果这些bullet都不符合要求,那么ei会是什么呢?这里似乎没有明确定义。

14
Clang拒绝这两个,而MSVC则接受这两个 https://godbolt.org/z/brWM388zc,所以三个主要的编译器都给出了三个不同的结果。不错。 - bolov
2
@bolov,我觉得Clang拒绝这个例子并不奇怪,因为Clang并不完全支持c++20。因此,在我的问题中我没有提到Clang。 - xmh0511
1
这个GCC测试来自他们的测试套件,包含几乎相同的代码,除了数组大小也是一个模板参数 - 在这种情况下它可以成功编译。 - interjay
@interjay 数组类型的值依赖边界是另一个案例,我想谈谈。我不知道第[1.5]条是否被认为是真实的?如果是真的,那么ei将是数组的元素,其中数组是C的子聚合体。这似乎有点模糊。 - xmh0511
1
@MarkusLenger Clang并不完全兼容c++20。我使用了最新的GCC来测试这段代码。除此之外,上面第一条评论中提到的这些编译器也可以编译这段代码。 - xmh0511
2个回答

2
进一步研究 P2082r1,我认为 GCC 在这个例子中是正确的。由于 Test 的子聚合物 T t [2] 有一个非相关值绑定的数组,因此相应的 e0 和 e1 分别是 t0t1。因此派生指南是 template<typename T> Test(T, T)
当这个指南用作类类型的构造函数以参与重载决议时,在这里应用了 [over.match.list],因为初始化程序是 {{1,2}},由于没有初始化列表构造函数,因此初始化列表的元素在这里被用作参数,如上所述,候选函数 template<typename T> Test(T, T) 有两个参数,但只有一个参数 ({1,2}) 被提供,因此重载决议失败,因此程序不合法。
然而,我仍然认为这个规则不够清晰,应该改进以使含义更加明确。

1
这是GCC版本11.2及以下的一个错误,在GCC 11.3中已得到修复。有关此错误的详细解释,请参见https://gcc.gnu.org/bugzilla/show_bug.cgi?id=101344
即使是早期版本的GCC也可以编译类似甚至更复杂的情况A(正如在问题评论中提到的)。
template<typename T, int N>
struct A { T t[N]; };

template<typename T>
struct B { T t[2]; };

int main()
{
   [[maybe_unused]] A a{{1, 2}}; //ok in GCC
   [[maybe_unused]] B b{{1, 2}}; //ok in GCC >= 11.3
}

示例:https://gcc.godbolt.org/z/4rcYj6fah


我认为GCC是正确的,请查看答案 https://dev59.com/TFEG5IYBdhLWcg3wW7bO#68256261 - xmh0511

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接