meryngii.neta

今日も新たな"ネタ"を求めて。

Uniform initialization syntaxの括弧問題についてのぼやき

C++11で導入されたUniform initialization syntaxは、C++の変数宣言にまつわる諸問題 (組み込み型とクラスの違い、Most Vexing Parse、narrow conversion)を解決する機能である。 しかし、Effective Modern C++でも取り上げられているように、 波括弧 {}だけではstd::vectorのようなクラスで呼び出せないコンストラクタが存在するという新たな問題が発生している。

std::vector<int> a(3, 123); // 3要素のベクタ: {123, 123, 123}
std::vector<int> b{3, 123}; // 2要素のベクタ: {3, 123}
std::vector<int> c{{3, 123}}; // 2要素のベクタ: {3, 123}

原因は周知の通り、std::vectorが「整数引数2個を受け取るコンストラクタ」と 「std::initializer_listを受け取るコンストラクタ」の両方を持っているが、 オーバーロード解決時にUniform initialization syntaxはinitializer_listをやたらと好むので、 前者のコンストラクタに一致させられないというものである。 特にテンプレート内の変数宣言では構築する型が分からない場合もあるので、 どちらを使うべきかという悩ましい問題になる。

どうもこの問題は言語設計上のミスだったのではないかという疑いを持ってしまう。 どう考えても普通のプログラムで一番よく使うのは普通のコンストラクタの呼び出しで、 それらに比べればstd::initializer_listを使うことは比較的稀だ。 丸括弧 ()を置き換えるためにUniform initialization syntaxを使うと、 我々はそこで暗黙のうちに「できるだけinitializer_listにマッチしよう」と主張してしまうことになる。 私の記憶では、当初Uniform initialization syntaxはあくまでInitializer listsの提案のおまけのようなものだったから、 こういう仕様になってしまった流れになった理由は何となく推察はできる。

ちなみに上の例だとbcも同じ意味になる。 bは他のコンストラクタよりも優先してinitializer_listを探し、 cはbraced-init-listを1つ取るコンストラクタを探し、 結果的に同じものにマッチすることになる。 私が思うに、bのような配列の初期化を認めず、 常にcのような形でinitializer_listを使うことを強制すればよかったのではないかという気がする。 配列を初期化するときは常に波括弧を2つ使うことになるが、 配列の初期化なんてそう頻繁に表れるわけではないのだから2文字増えるぐらいよいではないか、と思う。

まあでも言語機能として導入されてしまった以上、それを考慮してコードにするしかない、 という結論しか出ないのだが・・・。

追記

数年前に議論されていたらしい.

vector/arrayとUniform initialization+Initializer list - yohhoyの日記