web-dev-qa-db-fra.com

Le type incomplet n'est pas autorisé dans une classe, mais est autorisé dans un modèle de classe

Le code suivant est invalide:

struct foo {
    struct bar;
    bar x;        // error: field x has incomplete type
    struct bar{ int value{42}; };
};

int main() { return foo{}.x.value; }

Ceci est assez clair, puisque foo::bar est considéré comme incomplet au point où foo::x est défini.

Cependant, il semble exister une "solution de contournement" rendant valide la même définition de classe:

template <typename = void>
struct foo_impl {
    struct bar;
    bar x;        // no problems here
    struct bar{ int value{42}; };
};

using foo = foo_impl<>;

int main() { return foo{}.x.value; }

Ceci fonctionne avec tous les principaux compilateurs . J'ai trois questions à ce sujet:

  1. S'agit-il d'un code C++ valide, ou simplement d'une erreur des compilateurs?
  2. S'il s'agit d'un code valide, existe-t-il un paragraphe dans la norme C++ qui traite de cette exception?
  3. S'il s'agit d'un code valide, pourquoi la première version (sans template) est-elle considérée comme non valide? Si le compilateur peut comprendre la deuxième option, je ne vois pas pourquoi il ne pourrait pas déterminer la première.

Si j'ajoute une spécialisation explicite pour void:

template <typename = void>
struct foo_impl {};

template<>
struct foo_impl<void> {
    struct bar;
    bar x;        // error: field has incomplete type
    struct bar{ int value{42}; };
};

using foo = foo_impl<>;

int main() { return foo{}.x.value; } 

Encore une fois ne parvient pas à compiler .

20
Goran Flegar

Je pense que cet exemple est explicitement autorisé par 

17.6.1.2 Classes membres des modèles de classe [temp.mem.class]

1 Une classe membre d'un modèle de classe peut être définie en dehors de la définition du modèle de classe dans laquelle elle est déclarée . [Remarque: La classe de membre doit être définie avant sa première utilisation nécessitant une instanciation (17.8.1). Par exemple,

template<class T> struct A {
  class B;
};

A<int>::B* b1; // OK: requires A to be defined but not A::B
template<class T> class A<T>::B { };
A<int>::B b2; // OK: requires A::B to be defined

—Fin note]

Cela devrait fonctionne bien aussi:

template <typename = void>
struct foo_impl {
    struct bar;
    bar x;        // no problems here
};

template<typename T>
struct foo_impl<T>::bar{ int value{42}; };

using foo = foo_impl<>;

int main()
{
    return foo{}.x.value;
}
5
VTT

Plus de détails sur la réponse acceptée

Je ne suis pas sûr que la réponse acceptée soit la bonne explication, mais c’est la plus plausible pour l’instant. Extrapolant à partir de cette réponse, voici les réponses à mes questions initiales:

  1. S'agit-il d'un code C++ valide, ou simplement d'une erreur des compilateurs? [C'est un code valide.]
  2. S'il s'agit d'un code valide, existe-t-il un paragraphe dans la norme C++ qui traite de cette exception? [[temp.point]/4]
  3. S'il s'agit d'un code valide, pourquoi la première version (sans template) est-elle considérée comme non valide? Si le compilateur peut comprendre la deuxième option, je ne vois pas pourquoi il ne pourrait pas déterminer la première. [Parce que C++ est bizarre - il gère les modèles de classe différemment des classes (vous auriez probablement deviné celui-ci). ]

Quelques explications supplémentaires

Qu'est-ce que semble se produire

Lors de l'instanciation de foo{} dans main, le compilateur instancie une spécialisation (implicite) pour foo_impl<void>. Cette spécialisation fait référence à foo_impl<void>::bar à la ligne 4 (bar x;). Le contexte est dans une définition de modèle, il dépend donc d'un paramètre de modèle et la spécialisation foo_impl<void>::bar n'est évidemment pas encore instanciée auparavant. Toutes les conditions préalables pour [temp.point]/4 sont remplies et le compilateur génère les éléments suivants. code (pseudo) intermédiaire:

template <typename = void>
struct foo_impl {
    struct bar;
    bar x;        // no problems here
    struct bar{ int value{42}; };
};

using foo = foo_impl<>;

// implicit specialization of foo_impl<void>::bar, [temp.point]/4
$ struct foo_impl<void>::bar {
$     int value{42};
$ };
// implicit specialization of foo_impl<void> 
$ struct foo_impl<void> {
$     struct bar;
$     bar x;   // bar is not incomplete here
$ };
int main() { return foo{}.x.value; }

À propos de spécialisation

Selon [temp.spec]/4 :

Une spécialisation est une classe, une fonction ou un membre de classe instancié ou explicitement spécialisé.

l'appel à foo{}.x.value dans l'implémentation d'origine avec modèles est donc considéré comme une spécialisation (c'était quelque chose de nouveau pour moi).

À propos de la version avec spécialisation explicite

La version avec spécialisation explicite ne compile pas car il semble que:

si le contexte à partir duquel la spécialisation est référencée dépend d'un paramètre de modèle

ne tient plus, donc la règle de [temp.point]/4 ne s'applique pas.

0
Goran Flegar