考虑以下最小示例:
#include <iostream>
struct X {
X() { std::cout << "Default-ctor" << std::endl; }
X(std::initializer_list<int> l) {
std::cout << "Ilist-ctor: " << l.size() << std::endl;
}
};
int main() {
X a{};
X b({}); // reads as construct from {}
X c{{}}; // reads as construct from {0}
X d{{{}}}; // reads as construct from what?
// X e{{{{}}}}; // fails as expected
}
Godbolt示例
我对a、b和c没有任何疑问,一切都很清楚
但我不明白为什么d工作
d中的这对额外的背带代表什么?我查了一下C++20标准,但很难找到答案。clang和gcc都同意这个代码,所以是我错过了
要获得有关编译器功能的信息,一个很好的技巧是使用所有错误进行编译:CCD_ 1。让我们在这里看到输出(仅适用于d
(:
9.cpp:16:6: warning: constructor call from initializer list is incompatible with C++98
[-Wc++98-compat]
X d{{{}}}; // reads as construct from what?
^~~~~~
调用CCD_ 3。
9.cpp:16:8: warning: scalar initialized from empty initializer list is incompatible with
C++98 [-Wc++98-compat]
X d{{{}}}; // reads as construct from what?
^~
标量(int
(在内部{}
中初始化。所以我们有X d{{0}}
。
9.cpp:16:7: warning: initialization of initializer_list object is incompatible with
C++98 [-Wc++98-compat]
X d{{{}}}; // reads as construct from what?
^~~~
5 warnings generated.
CCD_ 7是从CCD_。所以我们有了X d{std::initializer_list<int>{0}};
!
这向我们展示了我们所需要的一切。额外的括号用于构造初始值设定项列表。
注意:如果你想添加额外的方括号,你可以通过调用复制/移动构造函数(或删除它(来添加,但C++编译器不会隐式地为你添加方括号以防止错误:
X d{X{{{}}}}; // OK
X e{{{{}}}}; // ERROR
我只想说明一下:
X d{ { {} }};
| | |
construct an | |
`X` from ... an initializer_list |
containing... int{}
列表初始化的规则是找到一个-Weverything
0构造函数并在可能的情况下使用它,否则。。。枚举构造函数并执行正常操作。
对于X{{}}
,即列表初始化:最外层的{}
是initializer_list
,其中包含一个元素:{}
,即0
。够直白的(虽然隐晦(。
但对于X{{{}}}
,使用最外层的{}
作为initializer_list
将不再有效,因为无法从{{}}
初始化int
。因此,我们转而使用构造函数。现在,其中一个构造函数使用initializer_list
,所以这有点像重新开始,只是我们已经剥离了一层大括号。
这就是为什么vector<int>{{1, 2, 3}}
也能工作,而不仅仅是vector<int>{1, 2, 3}
。但就像。。。不要。