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:
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 .
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;
}
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:
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). ]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; }
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).
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.