なんだか雲行きの怪しい雑記帖

ぐだぐだ日記とメモと,あと不定期更新

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

InitializerListとUniformInitialization(とMSVCと)

某勉強会の生放送を見てて,UniformInitializationの書き方とInitializarListの書き方がかぶってなんだか面倒くさいなぁと思っていたのですが,気になったこともあったので色々と試していました



UniformInitialization

C++から導入されたクラスですが,ローカルでインスタンスを初期化する際は「()」によりコンストラクタに引数を渡します.


#include <iostream>
using namespace std;

class foo
{
public :
foo(){ cout << "foo()" << endl; }
foo( int ){ cout << "foo(int)" << endl; }
};

int main()
{
// ローカル変数を初期化する際にコンストラクタに引数渡す
foo a( 0 );

return 0;
}

foo(int)


「using namespace std;」すんなよとか細かいところは今は目を瞑って下さい,本筋ではありません
さて,コンストラクタに引数を渡す場合は何も問題ありません.
ただ,引数を渡さない場合同じ書き方ができません.


// ~略~
int main()
{
// ローカル変数を初期化する際にコンストラクタに引数渡す
foo a( 0 );

// コンストラクタに引数を渡さない場合
foo b();// 正しくない(意図した通りではない),これはfoo型を返す引数なし関数bのプロトタイプ宣言と解釈される
foo c;// OK

return 0;
}

foo(int)
foo()


「b」も「c」も同じことをしたいのに,コンストラクタに引数を渡す渡さないで書き方が変わるのなんか嫌ですよね.
ただ,この「foo b();」はCとの互換のために変更することはできないので,思い切って「{}」を使ってコンストラクタ呼ぼうぜっていうのがUniformInitializationです.


// ~略~
int main()
{
// ローカル変数を初期化する際にコンストラクタに引数渡す
foo a{ 0 };

// コンストラクタに引数を渡さない場合
foo b{};// OK
foo c;// OK

return 0;
}

foo(int)
foo()
foo()




InitializerList

やりたい事的にはUniformInitializationとInitializerListにつながりはないです,とりあえず言っとくと.

C及びC++ではプリミティブの配列型では「= { ... }」と書くことで各要素を初期化できてました.

#include <iostream>
using namespace std;

int main()
{
int a[] ={ 0, 1, 2, 3, 4 };
for( const auto it : a )
{
cout << it << ",";
}

return 0;
}

0,1,2,3,4,


これをユーザ定義型とか他の型でも使えるようにしちゃおうってのがInitializerListです


#include <iostream>
#include <initializer_list>
using namespace std;

class foo
{
public :
foo( initializer_list<int> il )
{
cout << "foo(initializer_list):size=" << il.size() << endl;
for( const auto it : il ) cout << it << ",";
cout << endl;
}
};

int main()
{
foo a ={ 0, 1, 2, 3, 4 };

return 0;
}

foo(initializer_list):size=5
0,1,2,3,4,


MSVC2013Previewでは既にstd::vector等でこれらが実装されていますね.

これとStructのAggregateInitializationにより非常に強力な書き方が出来るんですが,今回はお題と外れるのでそれについては言及しません.
興味のある型はaggregate_initializationとか見ればいいんじゃないかな.

ちなみにこのInitializerList,実は「{...}」の項がinitializer_list型と解釈されるってのも覚えておくと色々便利.

#include <iostream>
#include <initializer_list>
using namespace std;

int main()
{
initializer_list<int> a = { 2, 3, 5, 7, 11 };
for( const auto it : a )
{
cout << it << ",";
}

return 0;
}

2,3,5,7,11,


若干余談になりますが,initializer_listはcbegin/cendではなくbegin/endを持つそうです.
initializer_listは変更不能なインスタンスしかないのでcbegin/cendを持つのが正しいように思えますが,これはrange-based forがbegin/endを持つコンテナを対象にする事との関係だとか.
実際,initializer_listのbegin/endはconst_iteratorと同じ型を返すので内部弄れない.



本題:UniformInitializationとInitializerList


さて,「{...}」はinitializer_list型として解釈されることは書きました.
なので,次の二つのローカル変数aとbの初期化は実質等しい動作をします.

#include <iostream>
#include <initializer_list>
using namespace std;

class foo
{
public :
foo( initializer_list<int> il )
{
cout << "foo(initializer_list):size=" << il.size() << endl;
for( const auto it : il ) cout << it << ",";
cout << endl;
}
};

int main()
{
foo a ={ 0, 1, 2, 3, 4 };
foo b( { 5, 6, 7, 8, 9 } );

return 0;
}

foo(initializer_list):size=5
0,1,2,3,4,
foo(initializer_list):size=5
5,6,7,8,9,


注意(NOTICE)!
ところでこのコード,MSVC12(MSVS2013Preview)だとコンパイル通らないコードなんですが,実は今回の記事はこういうMSVCの微妙なコンパイルエラーが起きるコードと起きないコードについて実験を行っています.
上記コードはg++とclang++では通るので,仕様的にはOKだと思います.

ここにUniformInitializationが入ってくると,bの書き方がこうなります

#include <iostream>
#include <initializer_list>
using namespace std;

class foo
{
public :
foo( initializer_list<int> il )
{
cout << "foo(initializer_list):size=" << il.size() << endl;
for( const auto it : il ) cout << it << ",";
cout << endl;
}
};

int main()
{
// 旧式? の書き方
foo b( { 5, 6, 7, 8, 9 } );

// uniform initialization+initializer_list的な書き方
foo c{ { 5, 6, 7, 8, 9 } };

return 0;
}

foo(initializer_list):size=5
5,6,7,8,9,
foo(initializer_list):size=5
5,6,7,8,9,


なんか気持ち悪くなってきましたね….
ところで,UniformInitializationとInitializerListの二つの規格が入ったのがほぼ同時期なためなのかは全くわかりませんが,cの書き方に関しては外側の「{}」は省略できちゃいます.
(と言うと語弊があるので補足すると,UniformInitializationの場合は引数はinitializer_listに変換され得るし,更にinitializer_listを食うコンストラクタがあった場合はそれを優先するという動作になるようで)


#include <iostream>
#include <initializer_list>
using namespace std;

class foo
{
public :
foo( int, int, int, int, int ){ cout << "foo(int,int,int,int,int)" << endl; }
foo( initializer_list<int> il )
{
cout << "foo(initializer_list):size=" << il.size() << endl;
for( const auto it : il ) cout << it << ",";
cout << endl;
}
};

int main()
{
// 旧式? の書き方
foo b( { 5, 6, 7, 8, 9 } );

// uniform initialization+initializer_list的な書き方
foo c{ { 5, 6, 7, 8, 9 } };

// uniform initializatiomただしinitializer_listが優先される
foo d{ 5, 6, 7, 8, 9 };

return 0;
}

foo(initializer_list):size=5
5,6,7,8,9,
foo(initializer_list):size=5
5,6,7,8,9,
foo(initializer_list):size=5
5,6,7,8,9,


はい,dの場合でもInitializerListの方が優先されてますね.
それにしてもうーん,今更だがこの言語むずいな….



実験:


MSVC2013PreviewになってUniformInitializationとかInitializerListとか対応したらしいので,早速試してみようということで実験しました.
一応g++やclang++など他のコンパイラだとコンパイル出来る出来ないの比較もちょっと.

ちょっと長いけどテストコードは以下.
MSVCだけ通るきもいのがありますが,まぁ置いとけ.

#include <iostream>
#include <initializer_list>
using namespace std;

class foo
{
public :
foo( initializer_list<int> il ) { }
};

int main()
{
// initializer_list
foo a ={ 0, 1, 2, 3, 4, 5 };

// initializer_listのリテラルを渡す場合
foo b( { 0, 1, 2, 3, 4, 5 } );

// 一旦ローカル変数に落としてから渡す場合
initializer_list<int> il ={ 0, 1, 2, 3, 4, 5 };
foo c( il );

// uniform initialization+initializer_list的な書き方
foo d{ { 0, 1, 2, 3, 4, 5 } };

// uniform initializatiom
foo e{ 0, 1, 2, 3, 4, 5 };

// 何やってるか意味不明だと思いますが,っていうか意味不明ですが,
// 通るコンパイラがあるんですよ
foo f{ { { { 0, 1, 2, 3, 4, 5 } } } };

// 一時変数的な扱いをした場合
auto g = foo( { 0, 1, 2, 3, 4, 5 } );
auto h = foo( il );
auto i = foo{ { 0, 1, 2, 3, 4, 5 } };
auto j = foo{ 0, 1, 2, 3, 4, 5 };
auto k = foo{ { { { 0, 1, 2, 3, 4, 5 } } } };

return 0;
}


比較コンパイラ(関係ないと思うけどOSも書いとく系)

  • MSVS Express 2013 Preview(Win8x86)

  • g++-4.7.2(Ubuntu12.04x86Desktop on VMPlayer)

  • clang++3.4-1 exp1(Ubuntu12.04x86Desktop on VMPlayer)



g++は4.7なのにclangは3.4かよとかいうツッコミに関しては,Clangはバージョンが古いとオレオレライブラリを食わせる時セグフォるので最新版入れてるだけだったりする.
他に使うメジャーなコンパイラはないと思うので,この3つで.

試した結果,MSVS2013Previewに関しては微妙な結果というかバグみたいなのがでて残念な感じになりました.











コンパイラMSVC12Previewg++-4.7.2clang++3.4-1 exp1
case : aOKOKOK
case : bCompilationErrorOKOK
case : cOKOKOK
case : dOKOKOK
case : eOKOKOK
case : fOKCompilationErrorCompilationError
case : gOKOKOK
case : hOKOKOK
case : iOKOKOK
case : jOKOKOK
case : kOKCompilationErrorCompilationError

一時変数にしたら通るあたりbは完全にMSVCのバグですねこれ…まあPreviewだししょうがない

それはそれといつconstexpr実装されるんですかね(ゲス顔
スポンサーサイト

FC2Ad

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。