列表初始化 (C++11 起)
从花括号初始化器列表初始化对象。
内容 |
[[编辑] 语法
[[编辑] 直接列表初始化
T 对象 { arg1, arg2, ... };
|
(1) | ||||||||
T { arg1, arg2, ... }
|
(2) | ||||||||
new T { arg1, arg2, ... }
|
(3) | ||||||||
类 { T 成员 { arg1, arg2, ... }; };
|
(4) | ||||||||
类:: 类() : 成员 { arg1, arg2, ... } {...
|
(5) | ||||||||
[[编辑] 复制列表初始化
T 对象 = { arg1, arg2, ... };
|
(6) | ||||||||
函数 ({ arg1, arg2, ... })
|
(7) | ||||||||
return { arg1, arg2, ... };
|
(8) | ||||||||
对象 [{ arg1, arg2, ... }]
|
(9) | ||||||||
对象 = { arg1, arg2, ... }
|
(10) | ||||||||
U ({ arg1, arg2, ... })
|
(11) | ||||||||
类 { T 成员 = { arg1, arg2, ... }; };
|
(12) | ||||||||
在下列情形中执行列表初始化
- 直接列表初始化(同时考虑显式和非显式构造函数)
- 复制列表初始化(同时考虑显式和非显式构造函数,但只能调用非显式构造函数)
U
不是正在被列表初始化的类型;U
的构造函数的形参是)[[编辑] 解释
类型为(可能带 cv 限定的) T
的对象的列表初始化的效果为
(C++20 起) |
- 如果
T
是聚合类且花括号初始化器列表,不含指示符初始化器列表,(C++20 起)具有相同或派生类型(可能带 cv 限定)的单个初始化器子句,则从该初始化器子句初始化对象(对于复制列表初始化,通过复制初始化;对于直接列表初始化,通过直接初始化)。 - 否则,如果
T
是字符数组且花括号初始化器列表具有适当类型的字符串字面量的单个初始化器子句,则数组如同寻常般从字符串字面量初始化。
- 否则,如果花括号初始化器列表为空且
T
是具有默认构造函数的类类型,则执行值初始化。
- 否则,如果
T
是 std::initializer_list 的特化,则对象按下述初始化。
- 否则,如果
T
是类类型,则考虑T
的构造函数,分两个阶段
- 所有接受 std::initializer_list 作为唯一实参,或作为首个实参且剩余实参具有默认值的构造函数均被考察,并针对类型为 std::initializer_list 的单个实参通过重载决议进行匹配。
- 若前一阶段未产生匹配,则
T
的所有构造函数参与针对由花括号初始化器列表的初始化器子句所构成的实参集的重载决议,限制是只允许非窄化转换。若此阶段产生显式构造函数作为复制列表初始化的最佳匹配,则编译失败(注意:在简单复制初始化中,根本不考虑显式构造函数)。
- 若前一阶段未产生匹配,则
(C++17 起) |
- 否则(如果
T
不是类类型),如果花括号初始化器列表只有一个初始化器子句,且T
不是引用类型,或是其所引用的类型与初始化器子句的类型相同或为其基类的引用类型,则T
为直接初始化(在直接列表初始化中)或复制初始化(在复制列表初始化中),但窄化转换不被允许。
- 否则,如果
T
是与初始化器子句的类型不兼容的引用类型
|
(直到 C++17) |
|
(C++17 起) |
- 否则,如果花括号初始化器列表没有初始化器子句,则
T
为值初始化。
[[编辑] 列表初始化 std::initializer_list
类型为 std::initializer_list<E> 的对象从初始化器列表构造,如同编译器生成并物化(C++17 起)类型为 “const E 的 N 数组” 的prvalue,其中 N 是初始化器列表中初始化器子句的数量;这被称为初始化器列表的后备数组。
后备数组的每个元素都用初始化器列表的对应初始化器子句复制初始化,且构造 std::initializer_list<E> 对象以引用该数组。为复制所选择的构造函数或转换函数必须在初始化器列表的语境中是可访问的。若初始化任何元素需要窄化转换,则程序为谬构。
后备数组拥有与其他临时对象相同的生存期,但从后备数组初始化 std::initializer_list 对象会延长数组的生存期,如同将引用绑定到临时量一样。
void f(std::initializer_list<double> il); void g(float x) { f({1, x, 3}); } void h() { f({1, 2, 3}); } struct A { mutable int i; }; void q(std::initializer_list<A>); void r() { q({A{1}, A{2}, A{3}}); } // The initialization above will be implemented in a way roughly equivalent to below, // assuming that the compiler can construct an initializer_list object with a pair of // pointers, and with the understanding that `__b` does not outlive the call to `f`. void g(float x) { const double __a[3] = {double{1}, double{x}, double{3}}; // backing array f(std::initializer_list<double>(__a, __a + 3)); } void h() { static constexpr double __b[3] = {double{1}, double{2}, double{3}}; // backing array f(std::initializer_list<double>(__b, __b + 3)); } void r() { const A __c[3] = {A{1}, A{2}, A{3}}; // backing array q(std::initializer_list<A>(__c, __c + 3)); }
是否所有后备数组都相异(即是否存储于非重叠对象中)是未指明的
bool fun(std::initializer_list<int> il1, std::initializer_list<int> il2) { return il2.begin() == il1.begin() + 1; } bool overlapping = fun({1, 2, 3}, {2, 3, 4}); // the result is unspecified: // the back arrays can share // storage within {1, 2, 3, 4}
[[编辑] 窄化转换
列表初始化通过禁止下列各项来限制允许的隐式转换
- 从浮点类型到整数类型的转换
- 从整数类型到浮点类型的转换,除非源是其值能准确地存储于目标类型中的常量表达式
- 从整数或无作用域枚举类型到不能表示原类型的所有值的整数类型的转换,除非
- 从指针类型或指向成员指针类型到 bool 的转换
[[编辑] 注解
每个初始化器子句都先于花括号初始化器列表中后随的任何初始化器子句。这与函数调用表达式的实参形成对比,函数调用表达式的实参是无顺序的(直到 C++17)不确定顺序的(C++17 起)。
花括号初始化器列表不是表达式,因此没有类型,例如 decltype({1, 2}) 是谬构。没有类型暗示着模板类型推导无法推导出匹配花括号初始化器列表的类型,所以给定声明 template<class T> void f(T);,表达式 f({1, 2, 3}) 是谬构。然而,模板形参可以以其他方式推导出来,例如 std::vector<int> v(std::istream_iterator<int>(std::cin), {}),其中迭代器类型由首个实参推导出来,但也用于第二个形参位置。为使用关键字 auto 的类型推导做出特殊例外,它在复制列表初始化中将任何花括号初始化器列表推导为 std::initializer_list。
同样因为花括号初始化器列表没有类型,当它用作到重载函数调用的实参时,重载决议的特殊规则适用。
聚合体直接从相同类型的单个初始化器子句的花括号初始化器列表复制/移动初始化,但非聚合体首先考虑 std::initializer_list 构造函数
struct X {}; // aggregate struct Q // non-aggregate { Q() = default; Q(Q const&) = default; Q(std::initializer_list<Q>) {} }; int main() { X x; X x2 = X{x}; // copy-constructor (not aggregate initialization) Q q; Q q2 = Q{q}; // initializer-list constructor (not copy constructor) }
一些编译器(例如 gcc 10)仅在 C++20 模式下才认为从指针或指向成员指针到 bool 的转换是窄化。
特性测试宏 | 值 | Std | 特性 |
---|---|---|---|
__cpp_initializer_lists |
200806L |
(C++11) | 列表初始化与 std::initializer_list |
[[编辑] 示例
#include <iostream> #include <map> #include <string> #include <vector> struct Foo { std::vector<int> mem = {1, 2, 3}; // list-initialization of a non-static member std::vector<int> mem2; Foo() : mem2{-1, -2, -3} {} // list-initialization of a member in constructor }; std::pair<std::string, std::string> f(std::pair<std::string, std::string> p) { return {p.second, p.first}; // list-initialization in return statement } int main() { int n0{}; // value-initialization (to zero) int n1{1}; // direct-list-initialization std::string s1{'a', 'b', 'c', 'd'}; // initializer-list constructor call std::string s2{s1, 2, 2}; // regular constructor call std::string s3{0x61, 'a'}; // initializer-list ctor is preferred to (int, char) int n2 = {1}; // copy-list-initialization double d = double{1.2}; // list-initialization of a prvalue, then copy-init auto s4 = std::string{"HelloWorld"}; // same as above, no temporary // created since C++17 std::map<int, std::string> m = // nested list-initialization { {1, "a"}, {2, {'a', 'b', 'c'}}, {3, s1} }; std::cout << f({"hello", "world"}).first // list-initialization in function call << '\n'; const int (&ar)[2] = {1, 2}; // binds an lvalue reference to a temporary array int&& r1 = {1}; // binds an rvalue reference to a temporary int // int& r2 = {2}; // error: cannot bind rvalue to a non-const lvalue ref // int bad{1.0}; // error: narrowing conversion unsigned char uc1{10}; // okay // unsigned char uc2{-1}; // error: narrowing conversion Foo f; std::cout << n0 << ' ' << n1 << ' ' << n2 << '\n' << s1 << ' ' << s2 << ' ' << s3 << '\n'; for (auto p : m) std::cout << p.first << ' ' << p.second << '\n'; for (auto n : f.mem) std::cout << n << ' '; for (auto n : f.mem2) std::cout << n << ' '; std::cout << '\n'; [](...){}(d, ar, r1, uc1); // has effect of [[maybe_unused]] }
输出
world 0 1 1 abcd cd aa 1 a 2 abc 3 abcd 1 2 3 -1 -2 -3
[[编辑] 缺陷报告
下列行为更改的缺陷报告被追溯应用到先前发布的 C++ 标准。
DR | 应用到 | 已发布行为 | 正确行为 |
---|---|---|---|
CWG 1288 | C++11 | 用花括号初始化器列表列表初始化引用,其中 单个初始化器子句总是将引用绑定到临时量 |
若有效则绑定到该初始化器 子句 |
CWG 1290 | C++11 | 后备数组的生存期未被正确指定 | 指定为与其他 临时对象相同 |
CWG 1324 | C++11 | 初始化首先被考虑用于从 {} 初始化 |
聚合初始化 首先被考虑 |
CWG 1418 | C++11 | 后备数组的类型缺少 const | 添加了 const |
CWG 1467 | C++11 | 相同类型聚合体和字符的初始化 数组被禁止;初始化器列表构造函数优先于 单子句列表的复制构造函数 |
相同类型初始化 允许;单子句 列表直接初始化 |
CWG 1494 | C++11 | 当用不兼容类型的初始化器子句列表初始化引用时,临时量是否 被创建为直接列表初始化或复制列表初始化是未指明的 它取决于 |
引用的 初始化种类 用于引用 |
CWG 2137 | C++11 | 当从 {X} 列表初始化 X 时,初始化器列表构造函数输给了复制构造函数 |
非聚合体首先考虑 初始化器列表 |
CWG 2252 | C++17 | 枚举可以从非标量值列表初始化 | 已禁止 |
CWG 2267 | C++11 | CWG 问题 1494 的决议阐明 临时量可以被直接列表初始化 |
它们是复制列表初始化 当列表初始化引用时 |
CWG 2374 | C++17 | 枚举的直接列表初始化允许过多的源类型 | 已受限 |
CWG 2627 | C++11 | 较大整数类型的窄位域可以提升到 较小整数类型,但它仍然是窄化转换 |
它不是 窄化转换 |
CWG 2713 | C++20 | 聚合类的引用不能 用指示符初始化器列表初始化 |
已允许 |
CWG 2830 | C++11 | 列表初始化不忽略顶层 cv 限定 | 忽略 |
CWG 2864 | C++11 | 溢出的浮点转换不是窄化 | 它们是窄化 |
P1957R2 | C++11 | 从指针/指向成员指针的转换 到 bool 不是窄化 |
被认为是窄化 |
P2752R3 | C++11 | 具有重叠生存期的后备数组不能重叠 | 它们可能重叠 |