Erreur lors de l’utilisation de l’initialisation de classe d’un membre de données non statique et d’un constructeur de classe nested

Le code suivant est assez sortingvial et je m’attendais à ce qu’il comstack bien.

struct A { struct B { int i = 0; }; B b; A(const B& _b = B()) : b(_b) {} }; 

J’ai testé ce code avec g ++ version 4.7.2, 4.8.1, clang ++ 3.2 et 3.3. Mis à part le fait que g ++ 4.7.2 segfaults sur ce code ( http://gcc.gnu.org/bugzilla/show_bug.cgi?id=57770 ), les autres compilateurs testés donnent des messages d’erreur qui n’expliquent pas grand chose.

g ++ 4.8.1:

 test.cpp: In constructor 'constexpr A::B::B()': test.cpp:3:12: error: constructor required before non-static data member for 'A::B::i' has been parsed struct B ^ test.cpp: At global scope: test.cpp:11:23: note: synthesized method 'constexpr A::B::B()' first required here A(const B& _b = B()) ^ 

Clang ++ 3.2 et 3.3:

 test.cpp:11:21: error: defaulted default constructor of 'B' cannot be used by non-static data member initializer which appears before end of class definition A(const B& _b = B()) ^ 

Rendre ce code compilable est possible et semble ne faire aucune différence. Il y a deux options:

 struct B { int i = 0; B(){} // using B()=default; works only for clang++ }; 

ou

 struct B { int i; B() : i(0) {} // classic c++98 initialization }; 

Ce code est-il vraiment incorrect ou les compilateurs sont-ils erronés?

Ce code est-il vraiment incorrect ou les compilateurs sont-ils erronés?

Eh bien non plus. Le standard a un défaut – il dit à la fois que A est considéré comme complet lors de l’parsing de l’initialiseur pour B::i , et que B::B() qui utilise l’initialiseur pour B::i ) peut être utilisé dans la définition de A C’est clairement cyclique. Considère ceci:

 struct A { struct B { int i = (A(), 0); }; A() noexcept(!noexcept(B())); }; 

Cela a une contradiction: B::B() est implicitement noexcept ssi A() ne lance pas et A() ne lance pas si B::B() n’est pas noexcept . Il existe plusieurs autres cycles et contradictions dans ce domaine.

Ceci est suivi par les questions essentielles 1360 et 1397 . Notons en particulier cette note dans le n ° 1397:

Peut-être le meilleur moyen de résoudre ce problème serait-il de le rendre mal formé pour qu’un initialiseur de données non statique utilise un constructeur par défaut de sa classe.

C’est un cas particulier de la règle que j’ai implémentée dans Clang pour résoudre ce problème. La règle de Clang est qu’un constructeur par défaut par défaut pour une classe ne peut pas être utilisé avant que les initialiseurs de données non statiques de cette classe soient analysés. Clang émet donc un diagnostic ici:

  A(const B& _b = B()) ^ 

… car Clang parsing les arguments par défaut avant d’parsingr les initialiseurs par défaut, et cet argument par défaut exigerait que les initialisateurs par défaut de B aient déjà été analysés (afin de définir implicitement B::B() ).

Peut-être que c’est le problème:

§12.1 5. Un constructeur par défaut qui est défini par défaut et non défini comme supprimé est implicitement défini lorsqu’il est utilisé (3.2) pour créer un object de son type de classe (1.8) ou lorsqu’il est explicitement défini par défaut après sa première déclaration

Ainsi, le constructeur par défaut est généré lors de la première consultation, mais la recherche échouera car A n’est pas complètement défini et B dans A ne sera donc pas trouvé.