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 };