Modification des règles pour les constructeurs protégés en C++17?

j'ai ce cas type:

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 et clang le compilent en mode C++11 et C++14. Les deux échouent en mode 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

(clang compilées à partir de la Branche principale, 2017-12-05.)

$ 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.

est-ce que ce changement de comportement fait partie de C++17 ou est-ce un bug dans les deux compilateurs?

59
demandé sur songyuanyao 2017-12-05 17:33:56

2 réponses

la définition de agrégat a changé depuis C++17.

Avant C++17

pas de classes de base

Depuis C++17

Non virtual, private, or protected (since C++17) classes de base

cela signifie, pour B et D , qu'ils ne sont pas du type agrégé avant C++17, puis pour B{} et D{} , valeur-initialisation seront effectuées, puis le défaut constructeur par défaut sera appelé; ce qui est bien, parce que le protected constructeur de classe de base pourrait être appelé par le constructeur de classe dérivée.

depuis C++17, B et D deviennent du type agrégé (parce qu'ils ont seulement la classe de base public , et notez que pour la classe D , le défaut explicitement le constructeur est autorisé pour le type d'agrégat depuis C++11), puis pour B{} et D{} , l'initialisation d'agrégat sera effectuée,

chaque élément de tableau direct public base, (since C++17) , ou membre de classe non statique, dans l'ordre de l'indice de tableau/de l'apparence dans la définition de classe, est copié-initialisé à partir de la clause correspondante de la liste initialiseur.

si le nombre de clauses d'initialisation est inférieur que le nombre de membres and bases (since C++17) ou liste d'initialiseur est complètement vide, les membres restants and bases (since C++17) sont initialisés by their default initializers, if provided in the class definition, and otherwise (since C++14) par des listes vides, conformément aux règles habituelles de liste-initialisation (qui effectue valeur-initialisation pour les types non-classe et les classes non-agrégées avec des constructeurs par défaut, et l'initialisation agrégée pour les agrégats). Si un membre d'un type de référence est l'un de ces membres, le programme est mal formé.

cela signifie que le sous-objet de la classe de base sera initialisé directement, le constructeur de B et D sont contournés; mais le constructeur par défaut de A est protected , puis le code échoue. (Notez que A n'est pas du type agrégat parce qu'il a un constructeur fourni par l'utilisateur.)

BTW: C (avec un constructeur fourni par l'utilisateur) n'est pas un type d'agrégat avant et après C++17, donc c'est très bien pour les deux cas.

51
répondu songyuanyao 2017-12-06 00:33:45

En C++17, les règles sur les agrégats a changé.

par exemple, vous pouvez faire ceci en C++17 maintenant:

struct A { int a; };
struct B { B(int){} };

struct C : A {};
struct D : B {};

int main() {
    (void) C{2};
    (void) D{1};
}

notez que nous n'héritons pas du constructeur. En C++17, C et D sont maintenant des agrégats même s'ils ont des classes de base.

avec {} , l'initialisation agrégée entre en jeu, et l'envoi de aucun paramètre sera interprété de la même manière que l'appel du constructeur par défaut du parent l'extérieur.

par exemple, l'initialisation agrégée peut être désactivée en changeant la classe D à ceci:

struct B { protected: B(){} };

struct D : B {
    int b;
private:
    int c;
};

int main() {
    (void) D{}; // works!
}

c'est parce que l'initialisation agrégée ne s'applique pas lorsque les membres ont des spécificateurs d'accès différents.

la raison pour laquelle avec = default fonctionne est parce que ce n'est pas un constructeur fourni par l'utilisateur. Plus d'informations à cette question .

22
répondu Guillaume Racicot 2017-12-05 15:20:47