このようなコードでは:
#include <iostream>
#include <initializer_list>
#include <string>
struct A
{
A() { std::cout << "2" << std::endl; }
A(int a) { std::cout << "0" << std::endl; }
A(std::initializer_list<std::string> s) { std::cout << "3" << std::endl; }
A(std::initializer_list<int> l) { std::cout << "1" << std::endl; }
};
int main()
{
A a1{{}};
}
なぜコンストラクターのstd::initializer_list<int>
仕様を呼び出すのですか?たとえば、コンストラクタをstd::initializer_list<double>
で定義すると、あいまいなコンパイルエラーが発生します。そのような構成の規則は何ですか、そしてなぜそれはテンプレート引数として数を持つstd::initializer_list
についてそれほど具体的ですか?
クラスに初期化子リストコンストラクターがある場合、_{whatever goes here}
_は引数として_{whatevergoeshere}
_を現在のコンストラクターに渡すことを意味します(初期化子リストコンストラクターがない場合は、_whatever goes here
_が引数として渡されます)。
それでは、設定を単純化して、他のコンストラクターを無視しましょう。コンパイラーはそれらを気にしないようです。
_void f(std::initializer_list<std::string> s);
void f(std::initializer_list<int> l);
_
f({{}})
には、このルールがあります
それ以外の場合、パラメータータイプがstd :: initializer_listであり、初期化子リストのすべての要素を暗黙的にXに変換できる場合、暗黙的な変換シーケンスは、リストの要素をXに変換するために必要な最悪の変換です。初期化子リストには要素、ID変換はありません。この変換は、初期化子リストコンストラクターの呼び出しのコンテキストでもユーザー定義の変換にすることができます。
ここでは、単一の要素_{}
_があり、_std::string
_を初期化するためにユーザー定義の変換が必要であり、int
の変換(ID)は必要ありません。したがって、int
が選択されます。
f({{{}}})
の場合、要素は_{{}}
_です。 int
に変換できますか?ルールは
- 初期化子リストに、それ自体が初期化子リストではない要素が1つある場合、暗黙の変換シーケンスは、要素をパラメーター型に変換するために必要なシーケンスです。
- .。
- 上に列挙したもの以外のすべての場合、変換は不可能です。
_std::string
_に変換できますか?はい、_std::initializer_list<char> init
_パラメーターを持つイニシャライザーリストコンストラクターがあるためです。したがって、今回は_std::string
_を選択します。
A a3({})
との違いは、このような場合、リストの初期化ではなく、_{}
_引数を使用した「通常の」初期化です(外側の中括弧がないため、ネストが1つ少なくなることに注意してください)。ここでは、2つのf
-関数が_{}
_で呼び出されています。また、両方のリストに要素がないため、どちらの場合もID変換が行われるため、あいまいさが生じます。
この場合のコンパイラーは、f(int)
も考慮し、他の2つの関数と連携します。ただし、int
パラメータが_initializer_list
_パラメータよりも悪いと宣言するタイブレーカーが適用されます。したがって、変換シーケンスの最良のグループには1つの候補ではなく、2つの候補が含まれるため、あいまいさの理由である半順序_{int} < {initializer_list<string>, initializer_list<int>}
_があります。
{}
からスカラー型(int
、double
、char*
など)への変換はID変換です。
{}
からstd::initializer_list
の特殊化以外のクラスタイプ(例:std::string
)への変換はユーザー定義の変換です。
前者は後者を打ち負かします。