指定された初期化子

指定された初期化は集約初期化の拡張であり、名前を使用してクラス型のメンバーを直接初期化できるようにします。

指定された初期化は、集合体の初期化の特殊なケースです。したがって、指定された初期化について書くことは、集合体の初期化について書くことを意味します。

集計の初期化

最初に:集計とは何ですか。集合体は配列とクラス型です。クラス型は、クラス、構造体、または共用体です。

C++20 では、次の条件はクラス型を保持する必要があります:

  • 非公開または保護された非静的データ メンバーなし
  • ユーザー宣言または継承されたコンストラクターなし
  • 仮想、プライベート、または保護された基本クラスなし
  • 仮想メンバー関数なし

次のプログラムは、集計の初期化の例です。

// aggregateInitialization.cpp

#include <iostream>

struct Point2D{
 int x; 
 int y;
};

class Point3D{
public:
 int x;
 int y;
 int z;
};

int main(){
 
 std::cout << std::endl;
 
 Point2D point2D{1, 2}; // (1)
 Point3D point3D{1, 2, 3}; // (2)

 std::cout << "point2D: " << point2D.x << " " << point2D.y << std::endl;
 std::cout << "point3D: " << point3D.x << " " << point3D.y << " " << point3D.z << std::endl;
 
 std::cout << std::endl;

}

(1) および (2) は、中括弧を使用して集計を直接初期化します。中括弧内の初期化子の順序は、メンバーの宣言順序と一致する必要があります。

C++11 の集約初期化に基づいて、C++20 で設計された初期化子を取得します。これまでのところ、指定された初期化子を完全にサポートしているのは Microsoft コンパイラだけです。

指定された初期化子

指定された初期化子を使用すると、名前を使用してクラス型のメンバーを直接初期化できます。共用体の場合、指定できる初期化子は 1 つだけです。集約の初期化に関しては、中括弧内の初期化子のシーケンスは、メンバーの宣言順序と一致する必要があります。

// designatedInitializer.cpp

#include <iostream>

struct Point2D{
 int x;
 int y;
};

class Point3D{
public:
 int x;
 int y;
 int z;
};

int main(){
 
 std::cout << std::endl;
 
 Point2D point2D{.x = 1, .y = 2}; // (1)
 Point3D point3D{.x = 1, .y = 2, .z = 3}; // (2)

 std::cout << "point2D: " << point2D.x << " " << point2D.y << std::endl;
 std::cout << "point3D: " << point3D.x << " " << point3D.y << " " << point3D.z << std::endl;
 
 std::cout << std::endl;

}

(1) と (2) は、指定された初期化子を使用して集計を初期化します。 .x や .y などの初期化子は、多くの場合、指定子と呼ばれます。

集合体のメンバーは、デフォルト値を持つことができます。このデフォルト値は、初期化子がない場合に使用されます。これは労働組合には当てはまりません。

// designatedInitializersDefaults.cpp

#include <iostream>

class Point3D{
public:
 int x;
 int y = 1; 
 int z = 2;
};

void needPoint(Point3D p) {
 std::cout << "p: " << p.x << " " << p.y << " " << p.z << std::endl;
}

int main(){
 
 std::cout << std::endl;
 
 Point3D point1{.x = 0, .y = 1, .z = 2}; // (1)
 std::cout << "point1: " << point1.x << " " << point1.y << " " << point1.z << std::endl;
 
 Point3D point2; // (2)
 std::cout << "point2: " << point2.x << " " << point2.y << " " << point2.z << std::endl;
 
 Point3D point3{.x = 0, .z = 20}; // (3)
 std::cout << "point3: " << point3.x << " " << point3.y << " " << point3.z << std::endl;
 
 // Point3D point4{.z = 20, .y = 1}; ERROR // (4) 
 
 needPoint({.x = 0}); // (5)
 
 std::cout << std::endl;

}

(1) すべてのメンバーを初期化しますが、(2) メンバー x の値を提供しません。したがって、x は初期化されません。 (3)や(5)のようにデフォルト値を持たないメンバーだけを初期化すればOKです。 z と y の順序が間違っているため、式 (4) はコンパイルされません。

指定された初期化子は、縮小変換を検出します。縮小変換は、精度の損失を含む値の変換です。

// designatedInitializerNarrowingConversion.cpp

#include <iostream>

struct Point2D{
 int x;
 int y;
};

class Point3D{
public:
 int x;
 int y;
 int z;
};

int main(){
 
 std::cout << std::endl;
 
 Point2D point2D{.x = 1, .y = 2.5}; // (1)
 Point3D point3D{.x = 1, .y = 2, .z = 3.5f}; // (2)

 std::cout << "point2D: " << point2D.x << " " << point2D.y << std::endl;
 std::cout << "point3D: " << point3D.x << " " << point3D.y << " " << point3D.z << std::endl;
 
 std::cout << std::endl;

}

(1) および (2) は、初期化 .y =2.5 および .z =3.5f が in への縮小変換を引き起こすため、コンパイル時エラーを生成します。

興味深いことに、C の指定された初期化子は、C++ の指定された初期化子とは異なる動作をします。

C と C++ の違い

C は、C++ でサポートされていないユースケースをサポートしています。 Cは許可します

  • 集計のメンバーを順不同で初期化する
  • ネストされた集計のメンバーを初期化する
  • 指定された初期化子と通常の初期化子を混在させる
  • 配列の指定された初期化

提案 P0329R4 は、これらのユースケースの自明の例を提供します:

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)

C と C++ の間のこの違いの論理的根拠も提案の一部です。配列指定子はラムダ式の構文と競合します。ネストされた指定子はめったに使用されません ." この論文は、集計の順不同の初期化のみが一般的に使用されると主張し続けています。

次は?

わお! C++98 では const、C++11 では constexpr、C++20 では consteval と constinit を取得しました。次回の投稿では、新しい C++20 指定子 consteval と constinit について、および const と constexpr との違いについて書きます。