このコードはGCC5.X、MSVCで正常にコンパイルされますが、GCC6.Xではエラーが発生します。
"初期化子リストから 'a'に変換すると、明示的なコンストラクター 'a :: a()'"、clang "選択されたコンストラクターはコピー初期化で明示的です"を使用します。
explicit
を削除するかa c{}
に変更すると問題は解決しますが、なぜこのように機能するのか興味があります。
class a
{
public:
explicit a () {}
};
struct b
{
a c;
};
int main() {
b d{};
}
b
は aggregate です。イニシャライザリストを使用して初期化すると、リスト内の要素がアグリゲートの最初のnメンバーを初期化します。ここでnは、リスト内の要素の数です。集合体の残りの要素はcopy-list-initializedです。
したがって、あなたの例では、c
はcopy-list-initializedになりますが、選択したコンストラクターがexplicit
の場合、形式が正しくないため、エラーが発生します。
関連する標準見積もりは次のとおりです。
[dcl.init.list]で指定されているイニシャライザリストによってアグリゲートが初期化されると、イニシャライザリストの要素がアグリゲートの要素のイニシャライザとして使用されます。アグリゲートの明示的に初期化された要素は次のように決定されます。
.。
—初期化子リストがinitializer-listの場合、集約の明示的に初期化された要素が最初になりますn集合体の要素。ここで、nは初期化子リスト内の要素の数です。
—それ以外の場合、初期化子リストは{}
である必要があり、明示的に初期化された要素はありません。
非ユニオンアグリゲートの場合、明示的に初期化された要素ではない各要素は、次のように初期化されます。
.。
—それ以外の場合、要素が参照でない場合、要素は空の初期化子リスト([dcl.init.list])からコピー初期化されます。
空の初期化子リストからc
をコピー初期化する効果については、
タイプ
T
のオブジェクトまたは参照のリスト初期化は次のように定義されます。
.。
—それ以外の場合、初期化子リストに要素がなく、T
がデフォルトのコンストラクターを持つクラス型である場合、オブジェクトは値で初期化されます。
value-initializeに対して、タイプ
T
のオブジェクトは次のことを意味します。
.。
—T
が(おそらくcv修飾された)クラス型であり、デフォルトコンストラクター([class.ctor])がないか、ユーザー提供または削除されたデフォルトコンストラクターである場合、オブジェクトはデフォルトで初期化されます。
default-initializeに対して、タイプ
T
のオブジェクトは次のことを意味します。
— Tが(おそらくcv修飾された)クラス型である場合、コンストラクターが考慮されます。該当するコンストラクターが列挙され([over.match.ctor])、イニシャライザー()に最適なコンストラクターがオーバーロード解決によって選択されます。このように選択されたコンストラクターは、オブジェクトを初期化するために、空の引数リストを使用して呼び出されます。
...コピー初期化の場合、候補関数はそのクラスのすべての変換コンストラクターです。
function-specifier
explicit
なしで宣言されたコンストラクターは、パラメーターの型(存在する場合)からクラスの型への変換を指定します。このようなコンストラクターは、変換コンストラクターと呼ばれます。
上記の例では、a
には変換コンストラクターがないため、過負荷の解決は失敗します。 [class.conv.ctor]/2の(非規範的な)例には、非常によく似たケースも含まれています
struct Z { explicit Z(); explicit Z(int); explicit Z(int, int); }; Z c = {}; // error: copy-list-initialization
c
のデフォルトのメンバー初期化子を提供することで、エラーを回避できます。
struct b
{
a c{}; // direct-list-initialization, explicit ctor is OK
};