前回の「変数を使う」に引き続き、C++における変数の使い方を紹介していきます。今回はC++11以降で導入された仕様である「一様初期化」と「型推論」を中心について説明します。
一様初期化
一様初期化というのは、簡単にいえば、コンストラクタの呼び出しと集成体の初期化を統一した表現方法のようなもので、波括弧 { } を使います。
コンストラクタの呼び出しというのは、前回紹介した次のような書き方のことです。
0 1 2 3 4 |
int i(123); double x(1.23); std::string s("abc"); |
集成体の初期化は、たとえば配列や構造体がそれにあたります。
0 1 2 3 4 5 6 7 8 9 |
struct A { int x; double y; }; A a = { 123, 4.56 }; int b[] = { 1, 2, 3, 4, 5 }; |
C++11以降では、これらはすべて波括弧を用いて初期化を行うことができるようになりました。
0 1 2 3 4 5 6 |
int i{123}; double x{1.23}; std::string s{"abc"}; A a{ 123, 4.56 }; int b[]{ 1, 2, 3, 4, 5 }; |
最初はちょっと違和感がありますが、慣れてしまえばどうということはありません。このような書き方にどんなメリットがあるかというと、関数に渡す実引数や返却値を記述する際にも使えますので、より簡潔に書くことができるようになっています。
0 1 2 3 4 5 |
std::string func() { return { 3, 'a' } ; // "aaa"を返す。 } |
例えば上のように、従来であればstd::string(3, ‘a’)のように書かなければなかったところを、型名を省略してより簡潔に書くことができるようになっています。
コンテナの初期化
std::vectorなど、C++にはいくつかのコンテナクラスが用意されています。それらのクラスは大変便利なのですが、以前は初期化時に要素を直接指定することができませんでした。そのため、いったん配列を介して初期化するか、コンテナの変数を作ってから要素を追加するしかありませんでした。
0 1 2 3 4 5 6 7 8 9 10 |
// 配列を介しての初期化 int array[] = { 1, 2, 3 }; std::vector<int> v1(array + 0, array + 3); // 後から要素を追加 std::vector<int> v2; v2.push_back(1); v2.push_back(2); v2.push_back(3); |
C++以降であれば、次のように直接初期化を行うことができるようになりました。
0 1 2 3 |
std::vector<int> v1 = { 1, 2, 3 }; std::vector<int> v2{ 1, 2, 3 }; // 一様初期化 |
一様初期化も使えますので、例えば関数にコンテナを渡す場合、あるいは関数からコンテナを返す場合には、直接実引数や返却値を組み立てるようになりました。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
std::vector<int> f() { return { 1, 2, 3 }; // 返却値 } void g(const std::vector<int>& v) { } int main() { g({ 1, 2, 3 }); // 実引数 ... |
型推論
C++11以降では「型推論」という強力な機能が追加されました。「型推論」は近年の多くのプログラミング言語に備わっている機能です。今回の最初に紹介した
0 1 2 3 4 |
int i(123); double x(1.23); std::string s("abc"); |
を型推論を使って書き換えると次のようになります。
0 1 2 3 4 |
auto i = 123; auto x = 1.23; auto s = "abc"s; |
最後の変数sだけは、”abc”で初期化してしまうとchar型の配列になってしまいますので、”abc”sで初期化しています。ここでは詳しい説明は割愛しますが、「s」という添字はそれがstd::string型であることを意味しています。
このように、型推論を使えば変数の型を自動的に初期値の型にしてくれます。ただし、型推論と一様初期化はちょっと相性が悪いので要注意です。たとえば、
0 1 2 3 4 |
auto i = {123}; auto x = {1.23}; auto s = {"abc"s}; |
このように書いてしまうと、変数i, x, sそれぞれの型はint型、double型、std::string型にはならず、std::initialize_listという初期値の並びを表現するためのクラス型になってしまいます。さらに困ったことには、
0 1 2 3 4 |
auto i{123}; auto x{1.23}; auto s{"abc"s}; |
のように等号を省略すると、C++14までとC++17以降では振る舞いが変わってしまいます。C++11とC++14では、上記は等号を付けた場合と同様すべてstd::initializer_listになりますが、C++17以降ではint型、double型、std::string型になります。このようにバージョン間での互換性の問題もあるため、型推論と一様初期化はなるべく併用しない方がよさそうです。
ちなみに、先ほど説明したようにC++14まではstd::initializer_listになるというのが仕様なのですが、現実の処理系ではどうもそうはならず、C++17以降と同様に振る舞うように見えます。このような状況がさらに事態をややこしくしているようです。