列表初始化 (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 | 生命週期重疊的後備陣列不能重疊 | 它們可能重疊 |