88 votes

Erreur lors de l'utilisation de l'initialisation en classe d'un membre de données non statique et d'un constructeur de classe imbriqué

Le code suivant est assez trivial et je m'attendais à ce qu'il compile 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 présente des erreurs 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++
};

o

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

Ce code est-il vraiment incorrect ou les compilateurs se trompent-ils ?

83voto

Richard Smith Points 3935

Ce code est-il vraiment incorrect ou les compilateurs se trompent-ils ?

Eh bien, ni l'un ni l'autre. La norme a un défaut -- elle dit à la fois que A est considéré comme complet alors que l'analyse syntaxique de l'initialisateur de B::i et que B::B() (qui utilise l'initialisateur de B::i ) peut être utilisé dans la définition de A . C'est clairement cyclique. Considérez ceci :

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

Il y a là une contradiction : B::B() est implicitement noexcept si A() ne lance pas, et A() ne lance pas si B::B() est no noexcept . Il existe un certain nombre d'autres cycles et contradictions dans ce domaine.

Ce suivi est assuré par des questions essentielles 1360 y 1397 . Notez en particulier cette note dans le numéro de base 1397 :

La meilleure façon de résoudre ce problème serait peut-être de faire en sorte que l'initialisation d'un membre de données non statique n'utilise pas le 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 pour une classe ne peut pas être utilisé avant que les initialisateurs de membres de données non statiques pour cette classe soient analysés. Par conséquent, Clang émet un diagnostic ici :

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

... parce que Clang analyse les arguments par défaut avant d'analyser les initialisateurs par défaut, et cet argument par défaut nécessiterait B pour que les initialisateurs par défaut de l'utilisateur aient déjà été analysés (afin de définir implicitement les initialisateurs par défaut de l'utilisateur). B::B() ).

0voto

fscan Points 358

C'est peut-être ça le problème :

§12.1 5. un constructeur par défaut qui n'est pas défini comme supprimé est implicitement défini lorsqu'il est odr- utilisé (3.2) pour créer un objet de sa classe type (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 recherche, mais la recherche échouera car A n'est pas complètement défini et B dans A ne sera donc pas trouvé.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X