- cpp11[meta cpp]
「初期化子リスト (initializer lists)」は、ユーザー定義型のオブジェクトに対して、波カッコによるリスト初期化を使用できるようにするようオーバーロードする機能である。
これによって、std::vector
のようなコンテナクラスに対しても、組み込み配列と同様に、波カッコによる簡易的な初期化構文を使用できる:
// 1, 2, 3の3要素を持つ配列オブジェクトを定義する
int ar[] = {1, 2, 3};
std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 {1, 2, 3};
// 再代入
v2 = {4, 5, 6};
ユーザー定義型に対してリスト初期化を許可するためには、<initializer_list>
ヘッダで定義されるstd::initializer_list
クラスのオブジェクトをパラメータとして、コンストラクタや代入演算子をオーバーロードする:
#include <vector>
#include <initializer_list>
template <class T>
class MyVector {
std::vector<T> data_;
public:
// リスト初期化用のコンストラクタ
MyVector(std::initializer_list<T> init)
: data_(init.begin(), init.end()) {}
};
int main()
{
MyVector<int> v = {1, 2, 3};
}
- init.begin()[link /reference/initializer_list/initializer_list/begin.md]
- init.end()[link /reference/initializer_list/initializer_list/end.md]
リスト初期化用のコンストラクタにexplicit
を付けた場合、代入演算子によるリスト初期化は許可せず、代入演算子を伴わないリスト初期化のみが許可される:
#include <vector>
#include <initializer_list>
template <class T>
class MyVector {
std::vector<T> data_;
public:
// リスト初期化用のコンストラクタ
explicit MyVector(std::initializer_list<T> init)
: data_(init.begin(), init.end()) {}
};
int main()
{
// MyVector<int> v = {1, 2, 3}; // コンパイルエラー!
MyVector<int> v {1, 2, 3}; // OK
}
- init.begin()[link /reference/initializer_list/initializer_list/begin.md]
- init.end()[link /reference/initializer_list/initializer_list/end.md]
- 波カッコ
{ }
を使用した初期化子のリストによるオブジェクトもしくは参照の初期化を、「リスト初期化 (list initialization)」と呼び、その初期化子を「初期化子リスト (initializer list)」と呼ぶ。初期化子リストは、カンマ区切りで要素を列挙する - 初期化子リストは、空であってもよい
初期化子リストは、以下の文脈で使用できる:
-
変数定義での初期化子リストによる初期化
struct X { X(std::initializer_list<int>) {} }; X x1 {1, 2, 3}; // 直接初期化して変数定義 X {1, 2, 3}; // 直接初期化して一時オブジェクトを定義 X x2 = {1, 2, 3}; // コピー初期化して変数定義
-
new
式での初期化子リストによる初期化new X {1, 2, 3}; // 動的記憶域でXオブジェクトを直接初期化
-
return
文X f() { return {1, 2, 3}; }
-
関数の引数
void f(X) {} f({1, 2, 3});
-
式の一部
std::vector<X> xs { {1, 2, 3}, // 初期化子リストのなかでさらに初期化子リストを使用する {4, 5, 6} };
-
基底クラスやメンバの初期化子
struct Y : X { std::vector<int> values; Y() : X {1, 2, 3}, values {4, 5, 6} {} };
-
代入演算子の右辺
struct X { X& operator=(std::initializer_list<int>) { return *this; } }; X x; x = {1, 2, 3};
-
初期化子リストに縮小変換が要求された場合、プログラムは不適格となる
struct X { X(std::initializer_list<int>) {} }; X x1 = {1, 2, 3}; // OK //X x2 = {1, 2, 3.0}; // コンパイルエラー!3.0をint型に縮小変換できない
-
縮小変換以外の型変換は許可される
struct X { X(std::initializer_list<double>) {} }; X x1 = {1, 2, 3}; // OK X x2 = {1, 2, 3.0}; // OK
以下の条件を満たすコンストラクタを、「初期化子リストコンストラクタ (initializer-list constructor)」と呼ぶ:
- 任意の型
E
を要素とするstd::initializer_list<E>
型のパラメータをひとつだけとり、そのほかのパラメータを持たない - もしくは、
std::initializer_list<E>
型のパラメータおよび、それ以降にデフォルト引数を持つ
-
デフォルトコンストラクタと初期化子リストコンストラクタがある場合、空の初期化子リストが渡された際にはデフォルトコンストラクタが呼び出される
#include <iostream> #include <initializer_list> struct X { X() { std::cout << "default constructor" << std::endl; } X(std::initializer_list<int>) { std::cout << "initializer-list constructor" << std::endl; } }; int main() { X x = {}; // 「default constructor」が出力される }
-
初期化子リストコンストラクタと、その初期化子リストの要素型と同じ型のパラメータリストを受け取るコンストラクタでは、初期化子リストコンストラクタが優先して呼び出される。そのような状況では、丸カッコでのコンストラクタ呼び出しが必要となる
struct X { X(std::initializer_list<double>) { std::cout << 1 << std::endl; } X(double d) { std::cout << 2 << std::endl; } }; X x1 = {3.0}; // 「1」が出力される
-
異なる要素型を持つ
std::initializer_list
型同士でオーバーロードができるstruct X { X(std::initializer_list<int>) { std::cout << 1 << std::endl; } X(std::initializer_list<double>) { std::cout << 2 << std::endl; } }; X {1, 2, 3}; // 「1」が出力される X {1.0, 2.0, 3.0}; // 「2」が出力される
-
初期化子リストが暗黙的に
std::initializer_list<E>
に型変換される際、実装はE
型の要素をN
個持つ配列を確保するかのように振る舞う。変換されたstd::initializer_list<E>
オブジェクトは、元となった初期化子リストの配列を参照する。以下のような初期化子リストの引数渡しがあった場合、struct X { X(std::initializer_list<double>) {} }; X x = {1, 2, 3};
実装は以下と等価の初期化を行う (実装が用意した
std::initializer_list
クラスがポインタの組を受け取れると仮定する):double __a[3] = {double{1}, double{2}, double{3}}; X x(std::initializer_list<double>(__a, __a+3));
元となった配列の寿命は、変換先の
std::initializer_list
オブジェクトと同じとなる
-
初期化子リストを
auto
で受けた場合、std::initializer_list
型に推論される。ただし、空の初期化子リストは推論に失敗するauto x1 = {1, 2, 3}; // x1の型はstd::initializer_list<int> //auto x2 = {}; // コンパイルエラー!x2の型を推論できない
-
単一要素の初期化子リストを
auto
で受けた場合、C++11ではstd::initializer_list<T>
型に推論されるが、C++17では直接初期化の場合T
型に推論されるよう仕様が変更されるので注意auto x{1}; // C++17ではxの型はintになる auto x = {1}; // C++11,17共に、xの型はstd::initializer_list<int>。
-
関数テンプレートのパラメータとして初期化子リストを受けとった場合は、
std::initializer_list
型には推論されないtemplate <class T> void f(T) {} int main() { f({1, 2, 3}); // コンパイルエラー!Tの型を推論できない }
-
std::initializer_list
の要素型のみを関数テンプレートで推論させることはできるtemplate <class T> void f(std::initializer_list<T>) {} f({1, 2, 3}); // OK : Tはint
-
初期化子リストに列挙した要素は、先頭から順番に評価されることが保証される
#include <iostream> #include <initializer_list> int f() { std::cout << 1 << std::endl; return 1; } int g() { std::cout << 2 << std::endl; return 2; } int h() { std::cout << 3 << std::endl; return 3; } int main() { std::initializer_list<int> init = { f(), g(), h() }; // 1, 2, 3の順で出力される }
C++の目標として、「組み込み型の振る舞いをユーザー定義型で定義できるようにする」というものがある。しかし、組み込み配列での波カッコを使用したリスト初期化は、ユーザー定義型に対してオーバーロードができなかった。それにより、std::vector
のようなコンテナクラスの初期化が使いにくいものとなっていた:
const int N = 3;
int ar[N] = {1, 2, 3};
std::vector<int> v(ar, ar + N);
この問題を解決するために、波カッコによるリスト初期化をユーザー定義型でオーバーロードする機能が求められ、std::initializer_list
クラスとオーバーロード機能が導入された。
- N1493 Braces Initialization Overloading
- N1509 Generalized Initializer Lists
- N1584 Regularizing Initialization Syntax
- N1701 Regularizing Initialization Syntax (revision 1)
- N1824 Extending Aggregate Initialization
- N1890 Initialization and initializers
- N1919 Initializer lists
- N2210 Initializer lists (Rev. 2)
- N2215 Initializer lists (Rev. 3)
- N2531 Initializer lists WP wording (Revision 2)
- N2575 Initializer Lists - Alternative Mechanism and Rationale
- N2640 Initializer Lists - Alternative Mechanism and Rationale (v. 2)
- N2672 Initializer List proposed wording
- CWG Issue 1030. Evaluation order in initializer-lists used in aggregate initialization