リスト初期化 (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のコンストラクタは2つのフェーズで考慮されます。
- std::initializer_listを唯一の引数として取るコンストラクタ、または残りの引数がデフォルト値を持つ場合に最初の引数として取るすべてのコンストラクタが検査され、std::initializer_list型の単一の引数に対してオーバーロード解決によってマッチングされます。
- 前の段階でマッチが見つからない場合、
Tのすべてのコンストラクタは、波括弧で囲まれた初期化子リストの初期化句からなる引数セットに対してオーバーロード解決に参加します。ただし、非縮小変換のみが許可されます。この段階でコピーリスト初期化の最良マッチとして明示的コンストラクタが生成された場合、コンパイルは失敗します(単純なコピー初期化では、明示的コンストラクタはまったく考慮されないことに注意してください)。
- 前の段階でマッチが見つからない場合、
| (C++17以降) |
- それ以外の場合(
Tがクラス型でない場合)、波括弧で囲まれた初期化子リストが単一の初期化句を持ち、Tが参照型でないか、またはその参照型が初期化句の型と同じか基底クラスである参照型である場合、Tは(直接リスト初期化では)直接初期化されるか、(コピーリスト初期化では)コピー初期化されますが、縮小変換は許可されません。
- それ以外の場合、
Tが初期化句の型と互換性のない参照型である場合
|
(C++17まで) |
|
(C++17以降) |
- それ以外の場合、波括弧で囲まれた初期化子リストに初期化句がない場合、
Tは値初期化されます。
[編集] std::initializer_list のリスト初期化
std::initializer_list<E>型のオブジェクトは、コンパイラが「N個のconst Eの配列」のprvalueを生成し、実体化したかのように(C++17 以降)初期化子リストから構築されます。ここで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}
[編集] 縮小変換
リスト初期化では、以下の暗黙の変換を禁止することで、許容される変換を制限します。
- 浮動小数点型から整数型への変換
- 浮動小数点型
Tから、浮動小数点変換ランクがTのそれよりも大きくも等しくもない別の浮動小数点型への変換。ただし、変換結果が定数式であり、以下のいずれかの条件が満たされる場合を除く。- 変換された値が有限であり、変換によってオーバーフローが発生しない。
- 変換前後の値がともに有限でない。
- 整数型から浮動小数点型への変換。ただし、ソースが定数式であり、その値がターゲット型に正確に格納できる場合を除く。
- 元のすべての値を表現できない整数型またはスコープなし列挙型から整数型への変換。ただし、以下のいずれかの場合を除く。
- ポインタ型またはポインタ・トゥ・メンバ型から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), {})の場合、イテレータ型は最初の引数によって推論されますが、2番目のパラメータ位置でも使用されます。特別な例外として、キーワード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++標準に遡って適用されました。
| DR | 適用対象 | 公開された動作 | 正しい動作 |
|---|---|---|---|
| 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 issue 1494の解決により、 一時オブジェクトが直接リスト初期化されうることが明確になりました。 |
それらはコピーリスト初期化される 参照をリスト初期化する際 |
| CWG 2374 | C++17 | enumの直接リスト初期化で、あまりにも多くのソース型が許容されていました。 | 制限された |
| CWG 2627 | C++11 | より大きな整数型の狭いビットフィールドはより小さな整数型に昇格できるが、 それでも縮小変換とされていました。 |
それは 縮小変換ではない |
| CWG 2713 | C++20 | 集約クラスへの参照が 指定初期化子リストによって初期化できませんでした。 |
許可 |
| CWG 2830 | C++11 | リスト初期化はトップレベルのcv修飾子を無視しませんでした。 | 無視する |
| CWG 2864 | C++11 | オーバーフローする浮動小数点変換は縮小変換ではありませんでした。 | それらは縮小変換です |
| P1957R2 | C++11 | ポインタ/ポインタ・トゥ・メンバからの変換 からboolへの変換は縮小変換ではありませんでした。 |
縮小変換と見なされる |
| P2752R3 | C++11 | 寿命が重なるバッキング配列は重なることができませんでした。 | それらは重なる場合があります |