Namespaces
Variants

List-initialization (since C++11)

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

花括号包围的初始化列表 初始化对象。

目录

语法

直接列表初始化

T 对象 { 参数1, 参数2, ... };

T 对象 {. 描述符1 = 参数1 , . 描述符2 { 参数2 } ... };

(自 C++20 起)
(1)
T { 参数1, 参数2, ... }

T {. 描述符1 = 参数1 , . 描述符2 { 参数2 } ... }

(自 C++20 起)
(2)
new T { 参数1, 参数2, ... }

new T {. 描述符1 = 参数1 , . 描述符2 { 参数2 } ... }

(自 C++20 起)
(3)
{ T 成员 { 参数1, 参数2, ... }; };

{ T 成员 {. 描述符1 = 参数1 , . 描述符2 { 参数2 } ... }; };

(自 C++20 起)
(4)
:: () : 成员 { 参数1, 参数2, ... } {...

:: () : 成员 {. 描述符1 = 参数1 , . 描述符2 { 参数2 } ... } {...

(自 C++20 起)
(5)

复制列表初始化

T 对象 = { 参数1, 参数2, ... };

T 对象 = {. 描述符1 = 参数1 , . 描述符2 { 参数2 } ... };

(C++20 起)
(6)
函数 ({ 参数1, 参数2, ... })

函数 ({. 描述符1 = 参数1 , . 描述符2 { 参数2 } ... })

(C++20 起)
(7)
return { 参数1, 参数2, ... };

return {. 描述符1 = 参数1 , . 描述符2 { 参数2 } ... };

(C++20 起)
(8)
对象 [{ 参数1, 参数2, ... }]

对象 [{. 描述符1 = 参数1 , . 描述符2 { 参数2 } ... }]

(C++20 起)
(9)
对象 = { 参数1, 参数2, ... }

对象 = {. 描述符1 = 参数1 , . 描述符2 { 参数2 } ... }

(C++20 起)
(10)
U ({ 参数1, 参数2, ... })

U ({. 描述符1 = 参数1 , . 描述符2 { 参数2 } ... })

(C++20 起)
(11)
{ T 成员 = { 参数1, 参数2, ... }; };

{ T 成员 = {. 描述符1 = 参数1 , . 描述符2 { 参数2 } ... }; };

(C++20 起)
(12)

列表初始化在以下情况下执行:

  • 直接列表初始化(同时考虑显式和隐式构造函数)
1) 使用大括号包围的初始化列表对命名变量进行初始化
2) 使用大括号包围的初始化列表对未命名临时对象进行初始化
3) 具有动态存储期对象的初始化,通过 new-expression 进行,其中初始化器为花括号包围的初始化列表
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
    • v U 的转换是非窄化转换。
(C++17 起)
  • 否则(如果 T 不是类类型),且花括号初始化列表仅包含一个初始化子句,同时 T 不是引用类型,或者是引用类型且其引用类型与初始化子句类型相同或是其基类,则对 T 执行 直接初始化 (在直接列表初始化中)或 拷贝初始化 (在拷贝列表初始化中),但不允许窄化转换。
  • 否则,如果 T 是一个与初始化子句类型不兼容的引用类型:
  • 生成一个 T 所引用类型的纯右值临时量,通过复制列表初始化该临时量,并将引用绑定到该临时量(若引用为非 const 左值引用则此操作失败)。
(C++17 前)
  • 生成一个纯右值。该纯右值通过复制列表初始化其结果对象,随后用于直接初始化引用(若引用为非 const 左值引用则此操作失败)。该临时量的类型为 T 所引用的类型 ,除非 T 为“到未知边界 U 数组的引用”,此时临时量的类型为声明 U x [ ] H x 的类型,其中 H 为初始化器列表 (C++20 起)
(C++17 起)
  • 否则,如果花括号包围的初始化列表没有初始化子句,则 T 将进行 值初始化

列表初始化 std::initializer_list

类型为 std:: initializer_list < E > 的对象从初始化列表构造时,如同编译器生成 物化 (C++17 起) 了一个类型为“ N const E 的数组”的 纯右值 ,其中 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}});
}
// 上述初始化将以大致等价于以下方式实现,
// 假设编译器能够通过一对指针构造initializer_list对象,
// 并理解`__b`不会在`f`函数调用结束后继续存在。
void g(float x)
{
    const double __a[3] = {double{1}, double{x}, double{3}}; // 后备数组
    f(std::initializer_list<double>(__a, __a + 3));
}
void h()
{
    static constexpr double __b[3] =
        {double{1}, double{2}, double{3}}; // 后备数组
    f(std::initializer_list<double>(__b, __b + 3));
}
void r()
{
    const A __c[3] = {A{1}, A{2}, A{3}}; // 后备数组
    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}); // 结果未指定:
                                              // 底层数组可能共享
                                              // {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 {}; // 聚合类型
struct Q     // 非聚合类型
{
    Q() = default;
    Q(Q const&) = default;
    Q(std::initializer_list<Q>) {}
};
int main()
{
    X x;
    X x2 = X{x}; // 拷贝构造函数(非聚合初始化)
    Q q;
    Q q2 = Q{q}; // 初始化列表构造函数(非拷贝构造函数)
}

某些编译器(例如 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}; // 非静态成员的列表初始化
    std::vector<int> mem2;
    Foo() : mem2{-1, -2, -3} {} // 构造函数中成员的列表初始化
};
std::pair<std::string, std::string> f(std::pair<std::string, std::string> p)
{
    return {p.second, p.first}; // return语句中的列表初始化
}
int main()
{
    int n0{};  // 值初始化(置零)
    int n1{1}; // 直接列表初始化
    std::string s1{'a', 'b', 'c', 'd'}; // 初始化列表构造函数调用
    std::string s2{s1, 2, 2};           // 常规构造函数调用
    std::string s3{0x61, 'a'}; // 初始化列表构造函数优先于(int, char)构造函数
    int n2 = {1}; // 拷贝列表初始化
    double d = double{1.2}; // 纯右值的列表初始化,然后拷贝初始化
    auto s4 = std::string{"HelloWorld"}; // 同上,自C++17起不创建临时对象
                                         //
    std::map<int, std::string> m = // 嵌套列表初始化
    {
        {1, "a"},
        {2, {'a', 'b', 'c'}},
        {3, s1}
    };
    std::cout << f({"hello", "world"}).first // 函数调用中的列表初始化
              << '\n';
    const int (&ar)[2] = {1, 2}; // 将左值引用绑定到临时数组
    int&& r1 = {1}; // 将右值引用绑定到临时int
//  int& r2 = {2}; // 错误:无法将右值绑定到非const左值引用
//  int bad{1.0}; // 错误:窄化转换
    unsigned char uc1{10}; // 正确
//  unsigned char uc2{-1}; // 错误:窄化转换
    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); // 具有[[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 时,初始化列表构造函数不敌拷贝构造函数 非聚合类型优先考虑初始化列表
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限定符 忽略顶层cv限定符
CWG 2864 C++11 溢出的浮点转换不被视为窄化转换 视为窄化转换
P1957R2 C++11 指针/成员指针到 bool 的转换不被视为窄化转换 视为窄化转换
P2752R3 C++11 生命周期重叠的后备数组不能重叠 允许重叠

参阅