初始化列表中的空大括号魔术
考虑以下最小示例:
#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 都同意这个代码,所以我错过了一些东西
回答
获取有关编译器功能的信息的一个很好的技巧是使用所有错误进行编译:
-Weverything. 让我们看看这里的输出(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?
^~~~~~
X::X(std::initializer_list) 叫做。
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.
std::initializer_list从 初始化{0}。所以我们有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{}
列表初始化的规则是找到一个initializer_list<T>构造函数并在可能的情况下使用它,否则......枚举构造函数并做正常的事情。
使用X{{}},即列表初始化:最外面的{}s 是initializer_list并且 this 包含一个元素:{},即0。足够直截了当(虽然神秘)。
但是对于X{{{}}},这不再使用最外层{}作为 the ,initializer_list因为您无法初始化intfrom {{}}。所以我们回退到使用构造函数。现在,其中一个构造函数需要一个initializer_list,所以这有点像重新开始,只是我们已经剥掉了一层大括号。
这就是为什么例如也vector<int>{{1, 2, 3}}可以工作,而不仅仅是vector<int>{1, 2, 3}. 但就像……不要。