web-dev-qa-db-fra.com

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. Outre le fait que g ++ 4.7.2 segfaults sur ce code ( http://gcc.gnu.org/bugzilla/show_bug.cgi?id=5777 ), les autres compilateurs testés donnent des messages d'erreur qui ne 'explique 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?

87
etam1024

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

Eh bien non plus. La norme a un défaut - elle indique à la fois que A est considéré comme complet lors de l'analyse 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 iff A() ne lance pas, et A() ne lance pas iff B::B() n'est pas pas noexcept. Il existe un certain nombre d'autres cycles et contradictions dans ce domaine.

Ceci est suivi par les problèmes principaux 136 et 1397 . Notez en particulier cette note dans le problème de base 1397:

Peut-être que le meilleur moyen de résoudre ce problème serait de le rendre mal formé pour un initialiseur de membre de données non statique d'utiliser 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 membres de données non statiques pour cette classe soient analysés. D'où Clang émet un diagnostic ici:

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

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

80
Richard Smith

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

§12.1 5. Un constructeur par défaut qui est par défaut et non défini comme supprimé est implicitement défini lorsqu'il est utilisé par odr (3.2) pour créer un objet de son type de classe (1.8) ou lorsqu'il est explicitement 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 à l'intérieur de A ne sera donc pas trouvé.

0
fscan