最終更新日時: 2025年07月10日 10時26分30秒 (JST)
Koichi Murase が更新

履歴 編集

# 指示付き初期化 [P0329R4](C++20)

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

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

## 概要

C++20では、波カッコによる集成体初期化においてメンバ名を指定して初期化が行える。

    struct Point3D { int x; int y; int z = 0; };
    struct Rect { Point3D p1; Point3D p2; };

    // 以下の例では、変数名と初期化子リストの間に=を書いても良い

    Point3D p1 {1, 2, 3};                // (1) OK これは通常の集成体初期化
    Point3D p2 {.x = 1, .y = 2, .z = 3}; // (2) OK (1)と同じ
    Point3D p3 {.x{1}, .y{2}, .z{3}};    // (3) OK (1)と同じ
    Point3D p4 {.x{1}};                  // (4) OK {.x{1}, .y{}, .z = 0}と同じ
    Point3D p5 {.z{3}};                  // (5) OK {.x{}, .y{}, .z{3}}と同じ
    Point3D p6 {.z = 3, .y = 2, .x = 1}; // (6) エラー: メンバの宣言順通りに並べなければならない

    Rect r1 { .p1{.x = 1, .y = 2, .z = 3} };     // OK
    Rect r1 { .p1.x = 1, .p1.y = 2, .p1.z = 3 }; // エラー

- この`.x`、`.y` などを指示子(designator)という
- `.x = 1`や`.y{2}`などを指示付き初期化子(designated initializer)という
- `{.x = 1, .y = 2, .z = 3}`などを指示付き初期化子リスト(designated initializer list)という

## 仕様

- 指示子は非静的メンバ変数の宣言順通りに並べなければならない
- 指示子は省略できる
  - 省略すると、デフォルトメンバ初期化子または`{}`で初期化する
- 暗黙の縮小変換はできない
- 通常の初期化子とは混在できない
- 指示付き初期化は`.a.x`のようにはネストできない
  - 別の波カッコによる集成体初期化がネストしている場合は問題ない
- 共用体に対する指示子は(最大で)1つであること
  - 共用体を指示付き初期化するときは、指示子は1つでなければならない
  - 無名共用体をメンバに持つ型を指示付き初期化するときは、その無名共用体のメンバに対応する指示子は最大で1つでなければならない
- 丸カッコによる集成体初期化では、指示付き初期化はできない

構文的には、指示付き初期化子リスト、初期化子リスト、空リストを含めて波カッコによる初期化である。指示付き初期化子リストも他と同様に`return`文や実引数などに書くことができる。

このことを活用すると、いわゆる名前付き引数のような書き方もできる。ただし、指示子が並び替えできないので少々不便である。

一方、メンバ名とその宣言順をコンパイル時にチェックできるという利点もある。通常の初期化子リストでは、型が同じメンバ同士を入れ替えてもエラーとはならない。

## 例

    #include <iostream>

    struct Point3D { int x; int y; int z = 0; };

    std::ostream& operator<<(std::ostream& os, const Point3D& p){
      return os << '(' << p.x << ',' << p.y << ',' << p.z << ')';
    }

    int main()
    {
      Point3D p1 {1, 2, 3};
      Point3D p2 {.x = 1, .y = 2, .z = 3};
      Point3D p3 {.x{1}, .y{2}, .z{3}};
      Point3D p4 {.x{1}};
      Point3D p5 {.z{3}};
      std::cout << p1 << '\n';
      std::cout << p2 << '\n';
      std::cout << p3 << '\n';
      std::cout << p4 << '\n';
      std::cout << p5 << '\n';
    }

### 出力

    (1,2,3)
    (1,2,3)
    (1,2,3)
    (1,0,0)
    (0,0,3)

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

最初にC99で指示付き初期化が導入され、C++にも取り込まれることになった。

ただし、次の機能はCでは有効だがC++には取り込まれていないなど、完全に互換性があるわけではない。

- 指示子の自由な並び替え(メンバの宣言順と一致しない順序)
  初期化子リストの要素は記述順に評価されるが、一方でC++ではメンバ変数は宣言順に初期化しなければならないため。
- 配列の指示付き初期化
  ラムダ式のキャプチャと競合するため。
- 指示付き初期化のネスト
  使用頻度が低いため。
- 指示子と通常の初期化子の混在

    // P0329R4から引用
    struct A { int x, y; };
    struct B { struct A a; };
    struct A a = {.y = 1, .x = 2}; // Cでは有効、C++20では無効
    int arr[3] = {[1] = 5};        // Cでは有効、C++20では無効
    struct B b = {.a.x = 0};       // Cでは有効、C++20では無効
    struct A a = {.x = 1, 2};      // Cでは有効、C++20では無効

## 参照

- P0329R4 Designated initialization