命名空间
变体
操作

列表初始化 (C++11 起)

来自 cppreference.cn
< cpp‎ | language
 
 
C++ 语言
通用主题
流程控制
条件执行语句
if
迭代语句 (循环)
for
范围 for (C++11 起)
跳转语句
函数
函数声明
Lambda 函数表达式
inline 说明符
动态异常规范 (直到 C++17*)
noexcept 说明符 (C++11 起)
异常
命名空间
类型
说明符
const/volatile
decltype (C++11 起)
auto (C++11 起)
constexpr (C++11 起)
consteval (C++20)
constinit (C++20)
存储期说明符
初始化
聚合初始化
列表初始化 (C++11 起)  
常量初始化
引用初始化

表达式
替用表示
字面量
布尔 - 整数 - 浮点
字符 - 字符串 - nullptr (C++11 起)
用户定义 (C++11 起)
工具
属性 (C++11 起)
类型
typedef 声明
类型别名声明 (C++11 起)
转型
内存分配
类专属函数属性
虚函数
override 说明符 (C++11 起)  
final 说明符 (C++11 起)
explicit (C++11 起)
static

特殊成员函数
模板
模板特化
形参包 (C++11 起)
杂项
 
 

花括号初始化器列表初始化对象。

内容

[[编辑] 语法

[[编辑] 直接列表初始化

T 对象 { arg1, arg2, ... };

T 对象{.des1 = arg1 , .des2 { arg2 } ... };

(C++20 起)
(1)
T { arg1, arg2, ... }

T {.des1 = arg1 , .des2 { arg2 } ... }

(C++20 起)
(2)
new T { arg1, arg2, ... }

new T {.des1 = arg1 , .des2 { arg2 } ... }

(C++20 起)
(3)
{ T 成员 { arg1, arg2, ... }; };

{ T 成员 {.des1 = arg1 , .des2 { arg2 } ... }; };

(C++20 起)
(4)
::() : 成员 { arg1, arg2, ... } {...

::() : 成员 {.des1 = arg1 , .des2 { arg2 } ...} {...

(C++20 起)
(5)

[[编辑] 复制列表初始化

T 对象 = { arg1, arg2, ... };

T 对象 = {.des1 = arg1 , .des2 { arg2 } ... };

(C++20 起)
(6)
函数 ({ arg1, arg2, ... })

函数 ({.des1 = arg1 , .des2 { arg2 } ... })

(C++20 起)
(7)
return { arg1, arg2, ... };

return {.des1 = arg1 , .des2 { arg2 } ... };

(C++20 起)
(8)
对象 [{ arg1, arg2, ... }]

对象 [{.des1 = arg1 , .des2 { arg2 } ... }]

(C++20 起)
(9)
对象 = { arg1, arg2, ... }

对象 = {.des1 = arg1 , .des2 { arg2 } ... }

(C++20 起)
(10)
U ({ arg1, arg2, ... })

U ({.des1 = arg1 , .des2 { arg2 } ... })

(C++20 起)
(11)
{ T 成员 = { arg1, arg2, ... }; };

{ T 成员 = {.des1 = arg1 , .des2 { arg2 } ... }; };

(C++20 起)
(12)

在下列情形中执行列表初始化

  • 直接列表初始化(同时考虑显式和非显式构造函数)
1) 用花括号初始化器列表初始化具名变量
2) 用花括号初始化器列表初始化无名临时量
3)new 表达式初始化具有动态存储期的对象,其中初始化器是花括号初始化器列表
4) 在不使用等号的非静态数据成员初始化器
5) 如果使用了花括号初始化器列表,则在构造函数的成员初始化列表
  • 复制列表初始化(同时考虑显式和非显式构造函数,但只能调用非显式构造函数)
6) 在等号后用花括号初始化器列表初始化具名变量
7) 在函数调用表达式中,花括号初始化器列表用作实参,且列表初始化初始化函数形参
8)return 语句中,花括号初始化器列表用作返回表达式,且列表初始化初始化返回对象
9) 在带有用户定义的 operator[]下标表达式中,其中列表初始化初始化重载运算符的形参
10)赋值表达式中,其中列表初始化初始化重载运算符的形参
11) 函数式转型表达式或其他构造函数调用,其中花括号初始化器列表用于代替构造函数实参。复制列表初始化初始化构造函数的形参(注意:本例中的类型 U 不是正在被列表初始化的类型;U 的构造函数的形参是)
12) 在使用等号的非静态数据成员初始化器

[[编辑] 解释

类型为(可能带 cv 限定的) T 的对象的列表初始化的效果为

  • 如果花括号初始化器列表含有指示符初始化器列表,且 T 不是引用类型,则 T 必须是聚合类。指示符初始化器列表的指示符中的已排序标识符必须形成 T 的直接非静态数据成员中的已排序标识符的子序列。聚合初始化被执行。
(C++20 起)
  • 如果 T 是聚合类且花括号初始化器列表,不含指示符初始化器列表,(C++20 起)具有相同或派生类型(可能带 cv 限定)的单个初始化器子句,则从该初始化器子句初始化对象(对于复制列表初始化,通过复制初始化;对于直接列表初始化,通过直接初始化)。
  • 否则,如果 T 是字符数组且花括号初始化器列表具有适当类型的字符串字面量的单个初始化器子句,则数组如同寻常般从字符串字面量初始化
  • 否则,如果花括号初始化器列表为空且 T 是具有默认构造函数的类类型,则执行值初始化
  • 否则,如果 T 是类类型,则考虑 T 的构造函数,分两个阶段
  • 若前一阶段未产生匹配,则 T 的所有构造函数参与针对由花括号初始化器列表的初始化器子句所构成的实参集的重载决议,限制是只允许非窄化转换。若此阶段产生显式构造函数作为复制列表初始化的最佳匹配,则编译失败(注意:在简单复制初始化中,根本不考虑显式构造函数)。
  • 否则,如果 T 是具有固定的底层类型 U枚举类型,花括号初始化器列表只有一个初始化器 v,且满足所有下列条件,则用将 v 转换为 U 的结果初始化枚举
    • 初始化为直接列表初始化。
    • v标量类型的。
    • v 可隐式转换为 U
    • vU 的转换是非窄化的。
(C++17 起)
  • 否则(如果 T 不是类类型),如果花括号初始化器列表只有一个初始化器子句,且 T 不是引用类型,或是其所引用的类型与初始化器子句的类型相同或为其基类的引用类型,则 T直接初始化(在直接列表初始化中)或复制初始化(在复制列表初始化中),但窄化转换不被允许。
  • 否则,如果 T 是与初始化器子句的类型不兼容的引用类型
  • 类型为 T 所引用的类型的 prvalue 临时量被复制列表初始化,且引用被绑定到该临时量(若引用是非 const 左值引用则失败)。
(直到 C++17)
  • 生成 prvalue。prvalue 通过复制列表初始化初始化其结果对象。然后 prvalue 被用于直接初始化引用(若引用是非 const 左值引用则失败)。临时量的类型是 T 所引用的类型,除非 T 是“未知边界的 U 数组的引用”,在此情况下临时量的类型是声明 U x[] H 中的 x 的类型,其中 H 是初始化器列表(C++20 起)
(C++17 起)
  • 否则,如果花括号初始化器列表没有初始化器子句,则 T值初始化

[[编辑] 列表初始化 std::initializer_list

类型为 std::initializer_list<E> 的对象从初始化器列表构造,如同编译器生成物化(C++17 起)类型为 “const EN 数组” 的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}

[[编辑] 窄化转换

列表初始化通过禁止下列各项来限制允许的隐式转换

  • 从浮点类型到整数类型的转换
  • 从浮点类型 T 到另一浮点类型的转换,其浮点转换等级既不大于也不等于 T 的浮点转换等级,除非转换结果是常量表达式且满足下列条件之一
    • 转换后的值是有限的,且转换不溢出。
    • 转换前后的值都不是有限的。
  • 从整数类型到浮点类型的转换,除非源是其值能准确地存储于目标类型中的常量表达式
  • 从整数或无作用域枚举类型到不能表示原类型的所有值的整数类型的转换,除非
    • 源是其宽度 w 小于其类型(或对于枚举类型,其底层类型)的位域,且目标类型能表示宽度为 w 且与原类型具有相同符号性的假想扩展整数类型的所有值,或
    • 源是其值能准确地存储于目标类型中的常量表达式
  • 从指针类型或指向成员指针类型到 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 具有重叠生存期的后备数组不能重叠 它们可能重叠

[[编辑] 参见