最終更新日時:
が更新

履歴 編集

基底クラスのメンバ変数に対する指示付き初期化を許可 [P2287R6](C++29)

このページはC++29に採用された言語機能の変更を解説しています。

のちのC++規格でさらに変更される場合があるため関連項目を参照してください。

概要

C++17では基底クラスを持つクラスも集成体として扱えるようになり、C++20では集成体を指示付き初期化できるようになった。しかし、指示付き初期化の指示子には直接の非静的メンバ変数しか指定できず、基底クラスを指定する方法もなかったため、基底クラスを持つ集成体には指示付き初期化を使用できなかった。

struct A { int a; };
struct B : A { int b; };

A x{.a = 1};    // OK (C++20)
B y{{1}, 2};    // OK (C++17)。ただし指示付き初期化は使えない

C++29では、指示付き初期化が次のように拡張される。

  • 指示子で、基底クラスのメンバ変数を直接指定できる。ただし、そのメンバ変数までの途中のすべての基底クラスが集成体である場合に限る
  • 指示なしの初期化子と指示付き初期化子の混在を、指示なしの初期化子がすべて先頭にあり、基底クラス部分の初期化に使われる場合に限り許可する

B{.a=1, .b=2}     // OK: 基底クラスのメンバ変数を直接指定できる
B{{.a=1}, .b=2}   // OK: 基底クラス部分を指示付き初期化する初期化子との混在もできる
B{.a{1}, .b{2}}   // OK
B{.b=2, .a=1}     // コンパイルエラー!宣言順に指定しなければならないのは従来どおり

仕様

  • designated-initializer-listの文法が、先頭に指示なしのinitializer-listを置けるよう拡張される。先頭の指示なし初期化子は、基底クラスの部分オブジェクトの初期化に使われる
  • 指示子の識別子には、直接の非静的メンバ変数に加えて、基底クラスの非静的メンバ変数も指定できる。ただし、そのメンバ変数を含むクラスに至るまでの、間にあるすべての基底クラスが集成体であること

    struct A { int a; };
    struct B : A { int b; };
    struct C : A { C(); int c; };
    struct D : C { int d; };
    
    B{.a=1, .b=2}; // OK: aは集成体である直接基底Aのメンバ変数
    C{.c=1};       // コンパイルエラー!Cは集成体ではない
    D{.a=1};       // コンパイルエラー!途中の基底クラスCが集成体ではない
    

  • 指示子の並びは、基底クラスのメンバ変数を含めた宣言順の部分列であること(基底クラスのメンバ変数が先)

  • 基底クラスのメンバ変数への指示付き初期化子は、対応する直接基底クラスの要素ごとにまとめられ、その基底クラス要素を指示付き初期化子リストで明示的に初期化したものとして、再帰的に初期化が行われる。明示的に初期化されない要素の扱いは従来どおりである
  • 指示子の名前は名前探索によって解決される。同じ名前のメンバ変数が複数の基底クラスにある場合、最派生のものが優先され、曖昧である場合は不適格となる
  • 機能テストマクロ__cpp_designated_initializersの値が202606Lに更新される
  • なお、この拡張によって、指示付き初期化子リストが基底クラスを持つ集成体のオーバーロード候補にも適合するようになるため、f(A)f(B)オーバーロードに対するf({.a=1})のような呼び出しは、C++26まではf(A)を呼び出していたのが、C++29では曖昧となり不適格になる

#include <iostream>

struct A {
  int a;
};

struct B : A {
  int b;
};

int main()
{
  // 基底クラスAのメンバ変数aを、指示子で直接指定して初期化する
  B x{.a = 1, .b = 2};
  std::cout << x.a << "," << x.b << std::endl;

  // 基底クラス部分を波カッコで指示付き初期化する書き方もできる
  B y{{.a = 3}, .b = 4};
  std::cout << y.a << "," << y.b << std::endl;
}

出力

1,2
3,4

この機能が必要になった背景・経緯

基底クラスを持つ集成体(C++17)と指示付き初期化(C++20)は別々に導入されたため組み合わせて使うことができず、基底クラスを持つ集成体はB{{1}, 2}B{1, 2}のような位置ベースの初期化しか使えなかった。

この提案の初期の版では、B{.A={.a=1}, .b=2}のように基底クラスそのものを名指しする構文が検討された。しかし、基底クラス名は単純な識別子とは限らず(テンプレートパラメータを含みうる)複雑になるため、この方向は採用されなかった。代わりに、b.a = 5; b.b = 6;のような代入の列と対応が取れるB{.a=5, .b=6}という直接指定が採用された。集成体の継承は「is-a」の関係よりもメンバの合成のために使われることが多く、基底クラスの存在という実装詳細を初期化側に意識させないためでもある。

また、集成体初期化では従来から波カッコの省略(B{1, 2})が許されていることとの一貫性から、基底クラス部分の波カッコは必須とはされず、省略可能とされた。

関連項目

参照