C++20嚐鮮:聚合體初始化變化

C++20 功能特性

提案

指派初始化器

P0329R4

括號形式的聚合體初始化

P0960R3

禁止有使用者宣告建構函式的聚合體

P1008R1

聚合類的類模板實參推導

P1816R0

P2082R1

聚合體

陣列型別

符合以下條件的類型別(常為

struct

union

沒有私有或受保護的直接

(C++17 起)

非靜態資料成員

沒有使用者宣告的建構函式

(C++11 前)

沒有使用者提供的建構函式(允許顯式預置或棄置的建構函式)

(C++11 起)

(C++17 前)

沒有使用者提供、繼承或 explicit 的建構函式(允許顯式預置或棄置的建構函式)

(C++17 起)

(C++20 前)

沒有使用者宣告或繼承的建構函式

(C++20 起)

沒有虛、私有或受保護

(C++17 起)

的基類

沒有虛成員函式

沒有預設成員初始化器

(C++11 起)

(C++14 前)

聚合體初始化

T

物件

=

{

實參1, 實參2, 。。。

};

(1)

T

物件

{

實參1, 實參2, 。。。

};

(2)

(C++11 起)

T

物件

=

{

.

指派符

=

實參1

,

.

指派符

{

實參2

}

。。。

};

(3)

(C++20 起)

T

物件

{

.

指派符

=

實參1

,

.

指派符

{

實參2

}

。。。

};

(4)

(C++20 起)

T

物件

(

實參1, 實參2, 。。。

);

(5)

(C++20 起)

聚合類的類模板實參推導

在C++17中使用帶CTAD的聚合,我們需要顯式的推導指引,現在沒有必要了。

templatestruct S{ T t; U u;};// 在 C++17,需要推導指引// template// S(T, U) -> S;int main(){ S s{1, 2。0}; // S}

如果有使用者提供了推導指引,則不參與CTAD

#include templatestruct MyData{ T data;};MyData(const char*) -> MyData;int main(){ MyData s1{“abc”}; // OK, MyData 使用推導指引 MyData s2{1}; // OK, :顯式模板引數 // MyData s3{1}; // Error, CTAD 不參與}

可以推導陣列型別

#include templatestruct Array{ T data[N];};int main(){ Array a{{1, 2, 3}}; // Array, 注意額外的括號 Array str{“hello”}; // Array}

大括號省略不適用於

待決名

的非陣列型別或

待決名

邊界的陣列型別(

待決名dependent name)

#include templatestruct Pair{ T first; U second;};templatestruct A1{ T data[N]; T oneMore; Pair p;};templatestruct A2{ T data[3]; T oneMore; Pair p;};int main(){ // A1::data 是待決名帶邊界的陣列型別 , A1::p 是待決名型別, 大括號省略不支援 A1 a1{{1,2,3}, 4, {5, 6}}; // A1 // A2::data 是待決名不帶邊界的陣列型別 , A2::p 不是是待決名型別, 大括號省略支援 A2 a2{1, 2, 3, 4, 5, 6}; // A2}

適用於包擴充套件。一個擴充套件包的尾隨聚合元素對應於所有剩餘元素

#include templatestruct Overload : Ts。。。{ using Ts::operator()。。。;};// C++20不需要推到指引Overload p{[](int){ std::cout << “called with int” << std::endl; }, [](char){ std::cout << “called with char” << std::endl; }}; // Overloadint main(){ p(1); // int p(‘c’); // char}

無尾隨元素包展開對應於沒有元素

templatestruct Pack : Ts。。。 { T x;};// 只能推導首元素int main(){ Pack p1{1}; // Pack Pack p2{[]{}}; // Pack // Pack p3{1, []{}}; // error}

包中的元素數量只被推導一次,但如果重複,型別應該完全匹配:

#include struct A{};struct B{};struct C{};struct D{ operator C(){return C{};}};templatestruct P : std::tuple, Ts。。。{};int main(){ P a {std::tuple{}, A{}, B{}, C{}}; // P // equivalent to the above, since pack elements were deduced for // std::tuple there‘s no need to repeat their types P b {std::tuple{}, {}, {}, {}}; // P // since we know the whole P type after std::tuple initializer, we can // omit trailing initializers, elements will be value-initialized as usual P c {std::tuple{}, {}, {}}; // P // error, pack deduced from first initializer is but got for // the trailing pack, implicit conversions are not considered P d {std::tuple{}, {}, {}, D{}};}

C++20嚐鮮:聚合體初始化變化

線上編譯測試

https://wandbox。org/nojs/gcc-headhttps://wandbox。org/nojs/clang-head

禁止使用使用者宣告的建構函式聚合

現在聚合型別不能有使用者宣告的建構函式。以前,聚合只允許有刪除的或預設的建構函式。這導致了帶有預設/刪除建構函式的聚合的怪異行為(它們是使用者宣告的,而不是使用者提供的)

// 以下型別在c++ 20中都不是聚合struct S{ int x{2}; S(int) = delete; // 使用者宣告的建構函式};struct X{ int x; X() = default; // // 使用者宣告的建構函式};struct Y{ int x; Y(); // // 使用者宣告的建構函式};Y::Y() = default;int main(){ S s(1); // 一直都是錯誤 S s2{1}; // C++17正確, C++20錯誤 X x{1}; // C++17正確, C++20錯誤 Y y{2}; // 一直都是錯誤}

https://wandbox。org/permlink/cPA3m3ppHv1h8eIT

C++20嚐鮮:聚合體初始化變化

線上測試

圓括號的聚會初始化

圓括號的聚合初始化現在與花括號初始化的工作方式相同,但是允許窄化轉換,不允許指定的初始化器,不允許為臨時物件延長生命週期,也不允許大括號省略。沒有初始化式的元素是

值初始化

的。這允許無縫使用工廠函式,如std::make_unique<>()/emplace()。

#include struct S{ int a; int b = 2; struct S2{ int d; } c;};struct Ref{ const int& r;};int GetInt(){ return 21;}int main(){ //S{0。1}; // error, 花括號不允許窄化(narrowing) S(0。1); // OK S{。a=1}; // OK //S(。a=1); // error, 圓括號不允許指定初始化 Ref r1{GetInt()}; // OK, 生命週期被延長 Ref r2(GetInt()); // 危險, 生命週期沒被延長 S{1, 2, 3}; // OK,花括號省略, 相當於 S{1,2,{3}} //S(1, 2, 3); // error, 不支援括號省略 // 沒有初始化器的值接受預設值或值初始化(T{}) S{1}; // {1, 2, 0} S(1); // {1, 2, 0} // make_unique 正常工作 auto ps = std::make_unique(1, 2, S::S2{3}); // 陣列也支援了 int arr1[](1, 2, 3); int arr2[2](1); // {1, 0}}

https://wandbox。org/permlink/w8OrhnuA6WJLb4GA

指派初始化器

每個

指派符

必須指名 T 的一個直接非靜態資料成員,而表示式中所用的所有

指派符

必須按照與 T 的資料成員相同的順序出現。

struct A { int x; int y; int z; };A a{。y = 2, 。x = 1}; // 錯誤:指派符的順序不匹配宣告順序A b{。x = 1, 。z = 2}; // OK:b。y 被初始化為 0

指派初始化器所指名的每個直接非靜態資料成員,從其指派符後隨的對應花括號或等號初始化器初始化。禁止窄化轉換。

指派初始化器可用於將聯合體初始化為其首個成員之外的狀態。只可以為一個聯合體提供一個初始化器。

union u { int a; const char* b; };u f = { 。b = “asdf” }; // OK:聯合體的活躍成員為 bu g = { 。a = 1, 。b = “asdf” }; // 錯誤:只可提供一個初始化器

對於非聯合體的聚合體中未提供指派初始化器的元素,按上述針對初始化器子句的數量少於成員數量時的規則進行初始化(如果提供預設成員初始化器則使用它,否則為空列表初始化):

struct A { string a; int b = 42; int c = -1;};A{。c=21} // 以 {} 初始化 a,這樣會呼叫預設建構函式 // 然後以 = 42 初始化 b // 然後以 = 21 初始化 c

如果以指派初始化器子句初始化的聚合體擁有一個匿名聯合體成員,那麼對應的指派初始化器必須指名該匿名聯合體的其中一個成員。

注意:亂序的指派初始化、巢狀的指派初始化、指派初始化器與常規初始化器的混合,以及陣列的指派初始化在 C 程式語言中受支援,但在 C++ 不允許。

struct A { int x, y; };struct B { struct A a; };struct A a = {。y = 1, 。x = 2}; // 合法 C,非法 C++(亂序)int arr[3] = {[1] = 5}; // 合法 C,非法 C++(陣列)struct B b = {。a。x = 0}; // 合法 C,非法 C++(巢狀)struct A a = {。x = 1, 2}; // 合法 C,非法 C++(混合)