std::initializer_list型

初期化子リストを受け取れるクラス

オブジェクトの初期化方法のひとつに、波括弧を使用するリスト初期化があります。
自作クラスで初期化子リストを受け取れるようにするにはstd::initializer_list型を引数に取るコンストラクタを定義します。
(<initializer_list>ヘッダファイル)

std::initializer_list型はC++11以降で使用可能です。
なお、集成体クラスは何もしなくてもリスト初期化が可能です。


#include <initializer_list>
#include <vector>

class TestClass
{
	std::vector<int> vec;

public:
	TestClass(std::initializer_list<int> initlist)
		:vec(initlist) {}
};

int main()
{
	//いずれもOK
	TestClass tc1 = { 1, 2, 3 };
	TestClass tc2{ 4, 5, 6 };
	TestClass tc3({ 7, 8, 9 });

	//代入も可能
	tc1 = { 7, 8, 9 };
}

上記コードではint型の初期化子リストを受け取れるコンストラクタを定義しています。
std::initializer_listクラステンプレートなので、任意のデータ型を指定して使用します。

コンストラクタにexplicitを指定すると、コピー初期化を禁止することができます。
(代入演算子での初期化や代入、関数の引数や戻り値のリスト初期化等ができなくなる)


#include <initializer_list>
#include <vector>

class TestClass
{
    std::vector<int> vec;

public:
    explicit TestClass(std::initializer_list<int> initlist)
        :vec(initlist) {}
};

int main()
{
	TestClass tc1 { 1, 2, 3 };
    TestClass tc2({ 4, 5, 6 });

	//=での初期化および代入は禁止
    //TestClass tc3 = { 7, 8, 9 };
    //tc1 = { 7, 8, 9 };
}

コンストラクタの優先順位

他のコンストラクタとの呼び出しがあいまいな場合はstd::initializer_listのコンストラクタ呼び出しが優先されます。
ただしクラスがデフォルトコンストラクタを持ち、空の初期化子リストを指定した場合はデフォルトコンストラクタが呼び出されます。


#include <iostream>
#include <initializer_list>
#include <vector>

class TestClass
{
    std::vector<int> vec;

public:
    TestClass()
    {
        std::cout << "default constructor" << std::endl;
    }
    TestClass(std::initializer_list<int> initlist)
        :vec(initlist)
    {
        std::cout << "initializer_list<int>" << std::endl;
    }
    TestClass(int x, int y)
        :vec({ x, y })
    {
        std::cout << "int, int" << std::endl;
    }
};

int main()
{
    TestClass tc1{ 1, 2 };
    //initializer_list<int>

    TestClass tc2(1, 2);
    //int, int
    
    TestClass tc3{};
    //default constructor
}

メンバ関数

上記のサンプルコードでは生成されたstd::initializer_listオブジェクトをそのままvectorクラスのコンストラクタに渡していますが、以下のメンバ関数で要素を指定して値を取り出すこともできます。

関数名 説明
size 要素数の取得(std::size_t型)
begin 先頭要素へのポインタの取得
end 末尾要素の次の要素へのポインタの取得

beginメンバ関数とendメンバ関数の戻り値はconstポインタです。
つまり、値を書き換えることはできません。


#include <iostream>
#include <initializer_list>
#include <vector>

class TestClass
{
	std::vector<int> vec;

public:
	TestClass(std::initializer_list<int> initlist)
		:vec(initlist) {}

	//vectorに初期化子リストの内容を追加する関数
	void append(std::initializer_list<int> initlist)
	{
		vec.insert(vec.end(), initlist.begin(), initlist.end());
	}

	void show()
	{
		for (size_t n = 0; n < vec.size(); n++) {
			std::cout << vec[n] << " ";
		}
		std::cout << std::endl;
	}
};

int main()
{
	TestClass tc = { 1, 2, 3 };
	tc.append({ 7, 8, 9 });
	tc.show();
	//1 2 3 7 8 9
}

初期化子リストと型推論

初期化子リストはそれ自体は式ではなく、データ型を持ちません。
型がないので、型を必要とする処理を行うことはできません。
例えばテンプレート引数decltypeなどに初期化子リストを渡すとコンパイルエラーになります。

例外として、autoで型推論をした場合はstd::initializer_list型のオブジェクトに変換されます。
また、std::initializer_list<T>型のテンプレート引数に初期化子リストを指定すると、型パラメータTは初期化子リストの要素の型に推論されます。


template<typename T>
void func1(T x) {}

template<typename T>
void func2(std::initializer_list<T> x) {}

int main()
{
	//引数から型推論できないのでエラー
	//func1({1, 2 });

	//明示的に型を指定すればOK
    func1<std::initializer_list<int>>({ 1, 2 });

	//Tはint型に推論される
	func2({ 1, 2 });

	//decltypeはできない
	//decltype({ 1, 2 });

	//std::initializer_list<int>型に変換される
	auto initlist = { 1, 2 };
}

単一の要素を持つ初期化子リストの型推論はC++17より前と以降で動作が異なります。
C++14まではstd::initializer_list型に推論されますが、C++17以降は単一の要素を持つ初期化子リストを直接初期化形式で型推論した場合はその単一の要素の型と推論されます。
要素を複数持つ初期化子リストを直接初期化で型推論した場合はコンパイルエラーとなります。

なお、初期化子リストの各要素の型が異なる場合は型推論ができないのでコンパイルエラーとなります。


//C++14まで
auto a{ 1 };        //std::initializer_list<int>
auto b = { 1 };     //std::initializer_list<int>
auto c{ 1, 2 };     //std::initializer_list<int>
auto d = { 1, 2 };  //std::initializer_list<int>

//C++17以降
auto e{ 1 };        //int
auto f = { 1 };     //std::initializer_list<int>
//auto g{ 1, 2 };   //コンパイルエラー
auto h = { 1, 2 };  //std::initializer_list<int>

//各要素の型が異なる場合はコンパイルエラー
//auto i = { 1, 2.0 };