[C++] 初始化包含 pair 的 STL array (aggregate-initialization)
在嘗試建立一個放 pair 的 std::array
時,發現語法似乎跟想像中不太一樣,一查才發現跟一般的 list-initialization 差別還不小。
背景知識
在 C++ 中,一個 aggregate 的定義 (C++20) 為:
- array type
- class types that has
- no user-declared or inherited constructors
- no private or protected non-static data members
- no virtual base classes
- no private or protected direct base classes
- no virtual member functions
以下的 struct 是一個 aggregate:
struct S {
int x;
struct Foo {
int i;
int j;
int a[3];
} b;
};
Aggregate 的 aggregate-initialization 是 list-initialization 的一種特殊形式,以下是幾個初始化 S
的範例:
S s1 = {1, {2, 3, {4, 5, 6}}};
S s2 = {1, 2, 3, 4, 5, 6}; // same, but with brace elision
S s3{1, {2, 3, {4, 5, 6}}}; // same, using direct-list-initialization syntax
S s4{1, 2, 3, 4, 5, 6}; // error until CWG 1270:
// brace elision only allowed with equals sign
比較特別的是 s2
省略了中間的大括號依然可以成功初始化,這是 aggregate-initialization 的特性之一。
正題
std::array
是只包含一個固定大小的 C-style array 的容器,類型為 aggregate,因此初始化是使用 aggregate-initialization。與同樣是 STL 的 std::vector
不同,沒有可以接收 initializer-list
類型的 constructor。
根據前面的範例,我們可以初始化一個簡單的 array:
std::array<int, 5> nums = {1, 2, 3, 4, 5};
但當我們想在 array 裡面放 pair 時,如果用與 vector 相同的方法初始化,則會出現錯誤:
std::array<std::pair<int, int>, 3> ids = {{0, 1}, {1, 2}, {2, 3}};
// error: too many initializers for 'std::array<std::pair<int, int>, 3>'
這是因為 std::array
的類型是 aggregate,而在 C++ 標準 9.4.2 中提到:
When an aggregate is initialized by an initializer list as specified in list-initialization (9.4.5), the elements of the initializer list are taken as initializers for the elements of the aggregate.
也就是最外層的大括號是 std::array
的 initializer,裡面的元素會分別作為 std::array
裡 data member 的 initializer。
如同前面所說,std::array<T, N>
只有一個 C-style array T[N]
的 data member。在上面的例子中,會有 3 個 initializer 給 std::array
,但實際上只有一個 data member,才會產生上面的錯誤。
若只在大括號內放剛好一個 initializer 會發生什麼事?{0, 1}
會被當成裝有 pair 的 array 的 initializer,而 0 跟 1 分別被當成 pair 的 initializer,因此會產生以下錯誤:
std::array<std::pair<int, int>, 3> ids = {{0, 1}};
// error: could not convert '0' from 'int' to 'std::pair<int, int>'
所以正確的做法是在 {0, 1}, {1, 2}, {2, 3}
的外面再加一層大括號,使其整個成為 C-style array 的 initializer:
std::array<std::pair<int, int>, 3> ids = {{{0, 1}, {1, 2}, {2, 3}}};
上面 std::array<int, 5>
的例子之所以能成立,是因為觸發了大括號省略 (brace elision) 的規則,將大括號內以逗號分隔的 initializer-clause 用於初始化 subaggregate 的子元素,在這裡 subaggregate 就是指 std::array
內的 C-style array。