私はこのテストケースを持っています:
struct A{ protected: A(){} };
struct B: A{};
struct C: A{ C(){} };
struct D: A{ D() = default; };
int main(){
(void)B{};
(void)C{};
(void)D{};
}
Gccとclangの両方が、C++ 11およびC++ 14モードでコンパイルします。どちらもC++ 17モードで失敗します。
$ clang++ -std=c++17 main.cpp
main.cpp:7:10: error: base class 'A' has protected default constructor
(void)B{};
^
main.cpp:1:22: note: declared protected here
struct A{ protected: A(){} };
^
main.cpp:9:10: error: base class 'A' has protected default constructor
(void)D{};
^
main.cpp:1:22: note: declared protected here
struct A{ protected: A(){} };
^
2 errors generated.
$ clang++ --version
clang version 6.0.0 (http://llvm.org/git/clang.git 96c9689f478d292390b76efcea35d87cbad3f44d) (http://llvm.org/git/llvm.git 360f53a441902d19ce27d070ad028005bc323e61)
Target: x86_64-unknown-linux-gnu
Thread model: posix
(master Branch 2017-12-05からコンパイルされたclang。)
$ g++ -std=c++17 main.cpp
main.cpp: In function 'int main()':
main.cpp:7:10: error: 'A::A()' is protected within this context
(void)B{};
^
main.cpp:1:22: note: declared protected here
struct A{ protected: A(){} };
^
main.cpp:9:10: error: 'A::A()' is protected within this context
(void)D{};
^
main.cpp:1:22: note: declared protected here
struct A{ protected: A(){} };
^
$ g++ --version
g++ (GCC) 8.0.0 20171201 (experimental)
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
この動作の変更はC++ 17の一部ですか、それとも両方のコンパイラのバグですか?
aggregate の定義はC++ 17以降に変更されました。
基本クラスなし
virtual, private, or protected (since C++17)
基本クラスなし
つまり、B
およびD
の場合、C++ 17より前では集約型ではなく、_B{}
_および_D{}
_の場合、 value-初期化 が実行され、次に デフォルトのデフォルトコンストラクター が呼び出されます。基本クラスのprotected
コンストラクターは、派生クラスのコンストラクターによって呼び出すことができるため、これは問題ありません。
C++ 17以降、B
およびD
は集約型になります(これらはpublic
ベースクラスのみを持つため、クラスD
の場合、明示的にC++ 11)以降、デフォルトのデフォルトコンストラクターが集約タイプに許可され、その後_B{}
_および_D{}
_に対して aggregate-initialization が実行されます。
各
direct public base, (since C++17)
配列要素、または非静的クラスメンバは、クラス定義内の配列添え字/出現順に、初期化子リストの対応する句からコピー初期化されます。初期化節の数がメンバーの数
and bases (since C++17)
より小さい場合、または初期化リストが完全に空の場合、残りのメンバーand bases (since C++17)
は空のリストによってby their default initializers, if provided in the class definition, and otherwise (since C++14)
が初期化されます。通常のリスト初期化ルール(デフォルトのコンストラクターを使用した非クラス型および非集約クラスの値初期化、および集約の集約初期化を実行します)に従います。参照型のメンバーがこれらの残りのメンバーの1つである場合、プログラムは不正な形式です。
つまり、基本クラスのサブオブジェクトは直接値で初期化され、B
とD
のコンストラクターはバイパスされます。ただし、A
のデフォルトコンストラクターはprotected
であるため、コードは失敗します。 (A
はユーザー指定のコンストラクターを持っているため、集約型ではないことに注意してください。)
BTW:C
(ユーザー提供のコンストラクターを使用)はC++ 17の前後では集約型ではないため、どちらの場合でも問題ありません。
C++ 17では、集計に関するルールが変更されました。
たとえば、今すぐC++ 17でこれを行うことができます。
struct A { int a; };
struct B { B(int){} };
struct C : A {};
struct D : B {};
int main() {
(void) C{2};
(void) D{1};
}
コンストラクターを継承していないことに注意してください。 C++ 17では、C
およびD
がベースクラスを持っている場合でも集約になりました。
{}
を使用すると、集約の初期化が開始され、パラメーターの送信は、外部から親のデフォルトコンストラクターを呼び出すのと同じように解釈されます。
たとえば、クラスのD
を次のように変更すると、集計の初期化を無効にできます。
struct B { protected: B(){} };
struct D : B {
int b;
private:
int c;
};
int main() {
(void) D{}; // works!
}
これは、異なるアクセス指定子を持つメンバーがいる場合、集計の初期化は適用されないためです。
= default
が機能する理由は、ユーザー提供のコンストラクタではないためです。詳細は この質問 で。