milk_spoonのブログ

さじの情報科学的活動、考えたこと、その他を雑記するためのブログです。

【C++】メンバ変数のstd::arrayの宣言時初期化で躓いた

STLちゃんと使ってますかー?
自分は結構std::array使います。
というか基本、C言語の人が生配列使いたいなーって思うところが全部std::arrayに置き換わる感じです。
今日もこんな感じでテーブルをstd::arrayで作って、色々と計算に使ったりしました。
(もちろん、テーブルの大きさは例として小さくしてありますが…)

#include<iostream>
#include<array>

int main()
{
	//配列と同じ形の初期化が使える  //int table[3] = {1, 2, 3};
	const std::array<int, 3> table = { 1, 2, 3 };

	//便利なメンバ関数や、イテレータなどの利用が可能
	for (int i = 0; i < table.size(); ++i)
	{
		std::cout << table[i] << "|";
	}
	std::cout << '\n';

}

コメントにも書いたとおり、生配列の記法+便利(というかCが不自由すぎ)な機能があるのでナイスです。

さて、今日はたくさんテーブルを作って計算してたのですが
その内の一つの処理が少し複雑だったので、std::arrayはクラスに持たせて複雑な処理はそこにまとめ、main内はすっきり書けるようにしたいなあと思いました。
ちゃちゃっとこんな感じで…

#include<iostream>
#include<array>

class Table
{
public:
	Table(){}

	//std::arrayの要素を出力
	void print()
	{
		for (size_t i = 0; i < m_data.size(); ++i)
		{
			std::cout << m_data[i] << "|";
		}
		std::cout << '\n';
	}

private:
	const std::array<int, 3> m_data = { 1, 2, 3 };//コンパイルエラー
};

int main()
{
	Table tbl;

	tbl.print();
}

え?動かない、というかコンパイルが通らないというのが今日の困り事でした。

ちなみにかなりコンパイラとか環境によって違う案件のようなので書いておくと、環境は
Microsoft Visual Studio Express 2013 for Windows Desktop
update 4
って感じです。

色々調べたら思ったよりも沼という感じだったので、結論から言うと下記のコードは動きます。

#include<iostream>
#include<array>

class Table
{
public:
	Table(){}

	void print()
	{
		for (size_t i = 0; i < m_data.size(); ++i)
		{
			std::cout << m_data[i] << "|";
		}
		std::cout << '\n';
	}

private:
    //コンパイルエラー  …(1)
	//const std::array<int, 3> m_data = { 1, 2, 3 };
    //コンパイルエラー  …(2)
	//const std::array<int, 3> m_data = {{ 1, 2, 3 }};
    //コンパイル…は通るが構文エラー表示  …(3)
	//const std::array<int, 3> m_data = std::array < int, 3 > { 1, 2, 3 };
    //OK  …(4)
	const std::array<int, 3> m_data = std::array < int, 3 > {{ 1, 2, 3 }};
};


int main()
{
	Table tbl;

	tbl.print();
}

コメントで色々試してみたりしたものを置いておきました。エラー内容やコンパイルOKかどうかが変わります。

そもそも(1)と(2)のm_data={~}型の初期化は使えません。
(1)か(2)のとき、C2797のコンパイルエラーが出ます。
これについての説明は以下です。
コンパイラ エラー C2797
この書き方が実装されてないみたいです。実装されていないなら仕方がないです。
下の方に記述がありますが、明示的にコンストラクタを呼び出して初期化、という感じで解決するようです。
じゃあ(3)の

const std::array<int, 3> m_data = std::array < int, 3 > { 1, 2, 3 }

で解決、と思いますよね

ところが、さっきの(1)および(3)ではC2797の他にもう一つエラーが出ます。
"IntelliSense: このサブオブジェクト初期化子では中かっこを省略できません"
というものです。
面白いのは(?)このエラーはコンパイルエラーにはならないところです。
(1)はコンパイルエラー、(3)はコンパイルは通り、プログラムが問題なく動くのです。
ということはコンパイルを妨げるのはC2797だけ、ということです。

中括弧が1重だと、中かっこを省略できません~のエラーが出ます。
この中かっこが1重か2重か?ってのが色々面白そうです。
実はC++11の仕様書(参考:N3337)的にはstd::array<N, T>は

std::array<N,T> = {initializer-list};

の形で初期化できるもの、と書いてあるので
(メンバ変数内宣言時の初期化とは言え)1重でエラー出すのはどうなんだ?
と思ったりもしないのですが、集成体(aggregate)の初期化時の中かっこの省略が、メンバ変数の宣言時初期化だとできないようになってるのかな?とか色々思いました。

ってこと考えてると†闇†が深くなってくるので、まあVS2013なら(4)でちゃんと動きます。というのが結論です。
ちなみにg++(GCC) 4.9.2では(2)でちゃんと動きますが、(1)はコンパイルが通りませんでした。
wandbox上でclangも試して見ましたが、(1), (2)のコンパイルは通るものの、-Wallオプション付きだとmain内での中かっこ1重の
初期化、および(1)について、警告が出る状態でした。
三者三様ですね。

コンパイラで色々違いが出たり、初期化子リストやaggregate等、調べたら結構勉強になりました。
詳しい所もまた書きたいと思います。