🧐

中括弧での初期化を少しひも解く

に公開1

以前中括弧での初期化について触れたことがありますが、実は中括弧の初期化に複数のタイプがあります。

  • 基本型の場合の初期化
  • 集成体初期化(aggregate initialization
    • 配列やクラス、構造体、共用体のような集成体の初期化
  • リスト初期化(list initialization
    • std::vectorstd::liststd::mapのようなコンテナの初期化

基本型の場合の初期化

基本型の中括弧での初期化は以下の記事でも紹介しましたが、丸括弧での初期化と違って、以下のように縮小変換の場合エラーになります。

long big_number{12342353252353};
int small_number1(big_number); // 警告
int small_number2{big_number}; // コンパイルエラー

集成体初期化(aggregate initialization)

集成体初期化は実は C でも C++ でも昔から使える初期化の方法で、一番見かけるのは、以下のような配列と構造体の初期化だと思います。

struct TestStruct {
    std::string name;
    int age;
    int level;
};

TestStruct test{"Ichiro", 30, 99};
int x[] = {1, 3, 5};

以下の記事で紹介したように、C++20 以降は構造体の場合に指示付き初期化が使えるようになりました。

TestStruct test{ .name = "Ichiro", .age = 30, .level = 99 };

構造体の中に構造体が定義されているもっと複雑な例も見てみましょう。

struct TestStruct {
    std::string name;
    int age;
    int level;
    
    struct Stats {
        int vitality;
        int strength;
        int intelligence;
    } stats;
};

TestStruct data1{"Ichiro", 30, 99, {1, 1, 99}};
TestStruct data2 {
    .name = "Ichiro",
    .age = 30,
    .level = 99,
    .stats = {
        .vitality = 1,
        .strength = 1,
        .intelligence = 99
    }
};

配列の場合は以下のように多次元の配列を初期化できます。

int array_2D[2][2] = {{1, 2}, {3, 4}};

でも多次元の配列も厳密には一次元の配列でもあるので、以下のような初期化もできます。

int array_2D[2][2] = {1, 2, 3, 4};

リスト初期化(list initialization)

std::vectorstd::liststd::mapのようなコンテナの場合は、C++11 までは中括弧で要素を直接的に指定することはできませんでした。

C++11 以前は、以下のように要素を1つずつ初期化するか、

std::vector<int> data;
data.push_back(0);
ints.push_back(1);
ints.push_back(2);

配列を作ってから、コンストラクタに渡すしかなかったのです。

const int arr[] = {0, 1, 2};
std::vector<int> data(arr, arr + sizeof(arr)/sizeof(arr[0]));

C++11 以降はリスト初期化という方法が追加されて、以下のように一行で要素を全部渡せるようになりました。

std::vector<int> data{0, 1, 2};

std::mapの初期化の場合は、もっと不便でした。
以下のように要素を1つずつ追加することはもちろんできましたが、

std::map<int, std::string> data;
data[0] = "Ichiro";
data[1] = "Jiro";
data[2] = "Saburo";

std::mapの要素が変数のペアなので、上記のような配列を作ってから渡す方法は一応できましたが、もっと複雑でした。

typedef std::map<int, std::string> MyMap;
const MyMap::value_type raw_data[] = {
   MyMap::value_type(0, "Ichiro"),
   MyMap::value_type(1, "Jiro"),
   MyMap::value_type(2, "Saburo"),
};
MyMap data(raw_data, raw_data + sizeof(raw_data) / sizeof(raw_data[0]));

かなり使いづらいですね。
C++11 以降はリスト初期化を使えば、以下のように簡単に初期化できるようになりました。

std::map<int, std::string> data {
    {0, "Ichiro"},
    {1, "Jiro"},
    {2, "Saburo"},
};

以下のように、集成体初期化とリスト初期化を合わせた初期化もできます。

std::map<int, TestStruct> data {
    {0, {"Ichiro", 30, 99, {1, 1, 99}}},
    {1, {"Jiro", 29, 70, {99, 99, 0}}},
    {2, {"Saburo", 28, 1, {5, 5, 5}}},
};

もっと複雑な例も存在すると思いますが、中括弧が増えるだけで基本は同じやり方で初期化できます。
構造体やコンテナを初期化する時に参考にしてください。


|cpp記事一覧へのリンク|

Discussion

yaito3014yaito3014

「リスト初期化」という用語は、基本型や集成体の波括弧による初期化を包含しています[1]。そのため、基本型のリスト初期化、集成体のリスト初期化、その他のリスト初期化、というように分けるべきかと思われます。

脚注
  1. https://eel.is/c++draft/dcl.init.list ↩︎