列表初始化 (C++11 起)
使用花括号初始值设定项列表初始化对象。
目录 |
[编辑] 语法
[编辑] 直接列表初始化
T object { arg1, arg2, ... };
|
(1) | ||||||||
T { arg1, arg2, ... }
|
(2) | ||||||||
new T { arg1, arg2, ... }
|
(3) | ||||||||
Class { T member { arg1, arg2, ... }; };
|
(4) | ||||||||
Class:: Class() : member { arg1, arg2, ... } {...
|
(5) | ||||||||
[编辑] 复制列表初始化
T object = { arg1, arg2, ... };
|
(6) | ||||||||
function ({ arg1, arg2, ... })
|
(7) | ||||||||
return { arg1, arg2, ... };
|
(8) | ||||||||
object [{ arg1, arg2, ... }]
|
(9) | ||||||||
object = { arg1, arg2, ... }
|
(10) | ||||||||
U ({ arg1, arg2, ... })
|
(11) | ||||||||
Class { T member = { 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
被直接初始化(在直接列表初始化中)或复制初始化(在复制列表初始化中),但窄化转换不允许。
- 否则,如果
T
是与初始值设定项子句的类型不兼容的引用类型
|
(C++17 前) |
|
(C++17 起) |
- 否则,如果花括号初始值设定项列表没有初始值设定项子句,则
T
被值初始化。
[编辑] 列表初始化 std::initializer_list
类型为std::initializer_list<E>的对象由初始化列表构造,如同编译器生成并实体化(C++17 起)了类型为“N个const E的数组”的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的转换视为窄化转换。
功能测试宏 | 值 | 标准 | 特性 |
---|---|---|---|
__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++ 标准。
缺陷报告 | 应用于 | 发布时的行为 | 正确的行为 |
---|---|---|---|
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 时,初始化列表构造函数输给复制构造函数当列表初始化 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的转换不是窄化转换 到bool不是窄化转换 |
被认为是窄化转换 |
P2752R3 | C++11 | 生命周期重叠的后备数组不能重叠 | 它们可能重叠 |