aggregate, aggregate initialization まとめ
C++ における aggregate と aggregate initialization についてのメモ。
まずバージョンごとの aggregate の定義から。
aggregate
C++03
aggregate とは*1
- 配列または
- 以下の要件を満たすクラスである
- ユーザー定義コンストラクタがない
- private または protected な非 static data member がない
- 基底クラスがない
- 仮想関数がない
C++11
aggregate とは*2
- 配列または
- 以下の要件を満たすクラスである
- ユーザー定義コンストラクタがない(明示的にデフォルト、または削除されたコンストラクタを除く)
- 非 static data member に対する brace-or-equal-initializer*3 がない
- private または protected な非 static data member がない
- 基底クラスがない
- 仮想関数がない
C++11 から default member initializer が導入されたが、同機能を用いた場合そのクラスは aggregate ではない。
C++14
aggregate とは*4
- 配列または
- 以下の要件を満たすクラスである。
- ユーザー定義コンストラクタがない(明示的にデフォルト、または削除されたコンストラクタを除く)
- private または protected な非 static data member がない
- 基底クラスがない
- 仮想関数がない
C++14 では 非 static data member に対する brace-or-equal-initializer があっても(default member initializerを用いても) aggregate と見なされる。
struct X{ // aggregate int x = 42; };
C++17
aggregate とは*5
- 配列または
- 以下の要件を満たすクラスである。
- ユーザー定義の明示的、または継承されたコンストラクタがない(明示的にデフォルト、または削除されたコンストラクタを除く)
- private または protected な非 static data member がない
- 仮想関数がない
- 仮想、private 及び protected な基底クラスがない
C++17 では基底クラスを持っていてもそれを public で継承しているのであれば aggregate であると見なされる。この時、aggregate initialization では、protected または private な基底クラスの member またはコンストラクタへのアクセスは許可されない。
struct X{ int a; }; struct Y:X{ // aggregate int b; };
またこの仕様により、ある aggregate クラスの基底型と convertible であるオブジェクトB
で aggregate クラスD
のオブジェクトの初期化を行える。
struct B{}; struct D:B{}; struct A{ operator B() { return {}; } }; D{B{}}; // OK: C++17 D{A{}}; // OK: C++17
この機能を利用して例えば aggregate クラスが基底クラスを持つかどうか検出する事ができる。
まずテンプレートパラメータの適切な基底クラス型に変換できるクラステンプレートを作成する。
template<class T> struct any_base{ operator T() = delete; template<class U, class = std::enable_if_t<std::is_base_of_v<U,T>>> operator U(); };
次にテンプレートパラメータT
がany_base
型の値から constructible な aggregate であるかどうかを検出する。
template<class, class = void> struct has_any_base : std::false_type{}; template<class T> struct has_any_base<T, std::void_t<decltype(T{any_base<T>{}})>> : std::true_type {};
以下に動作の概要を示す
- implicit conversion によって
T
の初期化を試みる - any_base を
T
そのものに変換する implicit conversion の member function は削除されているため any_base からT
の値は取得できない。 - any_base から
T
の値は取得できないためT
のそれ以外の初期化方法を考慮する必要があるが、返される型はstd::is_base_of
によって継承関係が要求されているため、その要件を唯一満たすのは、aggregate クラスの基底型のオブジェクトでその aggregate クラスの派生型のオブジェクトを初期化する事である。- それが不可能である場合、推論から外れる
- それが可能である場合、
U
は aggregate クラスT
の基底型に推論される
- 候補がある場合、substitution に成功、候補がない場合、substitution に失敗する。
C++20
aggregate initialization
aggregate 型のオブジェクトは、braced-init-list を使って初期化する事ができる。構文は以下の通りである。
T object = { arg1, arg2, ...};
C++11 で uniform initialization が導入されたため、C++11 以降では以下の構文も適用できる。
T object{arg1, arg2, ...};
C++20 で Designated initializers が導入されたため、C++20 以降では以下の構文も適用できる。
T object = { .designator = arg1, .designator{arg2}...}; T object{ .designator = arg1, .designator{arg2}...};
Designated initializers
C++20 から追加される Designated initializers の構文を以下に示す。n4687 11.6 Initializers [dcl.init] から抜粋
各はT
の direct non-static member に名前を付ける必要があり、式で使用される全てのはT
の data member と同じ順序で現れなければならない。
struct X{ int x; int y; int z; }; A a{.y = 2, .x = 1}; // エラー! designator の順序が宣言の順序と一致しない A b{.x = 1, .z = 2}; // OK: b.y は 0で初期化される
を用いて初期化する時、それらは の後に続く、それぞれが対応する によってコピー初期化される。もし が または の形式であり、式を変換するために narrowing conversion*7 が必要である場合プログラムは ill-formed となる。designated initializer を利用して、union を以下のように最初の状態以外の状態に初期化する事ができる。union に対しては、 を1つだけ指定できる。
union U{ int a; const char* b; }; U f = {.b = "abcd"}; // OK: f の active member は b U g = {.a = 1, .b = "abcd"}; // エラー!1つの initializer のみ利用できる
非 union aggregate である場合、明示的に初期化された要素ではない各要素は次のように初期化される。
- 要素にデフォルトの default member initializer がある場合、要素はその initializer から初期化される。
- それ以外の場合、要素が参照できない場合、要素は空の initializer list からコピー初期化される。
- それ以外の場合、プログラムは ill-formed である。
struct A { int x; struct B { int i; int j; } b; } a = { 1, { 2, 3 } }; // initializes a.x with 1, a.b.i with 2, a.b.j with 3
struct base1 { int b1, b2 = 42; }; struct base2 { base2() { b3 = 42; } int b3; }; struct derived : base1, base2 { int d; }; derived d1{{1, 2}, {}, 4}; // initializes d1.b1 with 1, d1.b2 with 2, d1.b3 with 42,d1.d with 4 derived d2{{}, {}, 4}; // initializes d2.b1 with 0, d2.b2 with 42, d2.b3 with 42, d2.d with 4
union aggregate であり、initializer list が空の場合
- 任意の variant member が default member initializer を持つ場合、その member はその default member initializer から初期化される。
- それ以外の場合は、共用体の最初のメンバー(存在する場合)が空の初期化子リストからコピー初期化される。
指定された で初期化された aggregate が匿名の union member を持つ場合、対応する designated initializer は、その匿名化された union の member の1つに名前を付ける必要がある。また、順序が不定である初期化、ネストされた designated initialization、指定された initializer と通常の initializer の混合、および配列の指定された初期化は全てC99でサポートされる機能であるが、C++20では許可されない。
struct A { int x, y; }; struct B { struct A a; }; struct A a = {.y = 1, .x = 2}; // valid C, invalid C++ (out of order) int arr[3] = {[1] = 5}; // valid C, invalid C++ (array) struct B b = {.a.x = 0}; // valid C, invalid C++ (nested) struct A a = {.x = 1, 2}; // valid C, invalid C++ (mixed)
*1:C++ Standard - ANSI ISO IEC 14882 2003 8.5.1 Aggregates [dcl.init.aggr]/paragraph 1
*2:n3337 8.5.1 Aggregates [dcl.init.aggr]/paragraph 1
*3:= assignment-expression または = braced-init-list または braced-init-list
*4:n4140 8.5.1 Aggregates [dcl.init.aggr]/paragraph 1
*5:n4660 11.6.1 Aggregates [dcl.init.aggr]/paragraph 1
*6:執筆時現在では不確定要素を含むため後に変更される可能性がある
*7:11.6.4 List-initialization [dcl.init.list]/paragraph 7