Type incomplète n'est pas autorisé dans une classe, mais il est permis à un modèle de classe

le code suivant n'est pas valide:

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

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

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

cependant, il semble y avoir une "solution de contournement" qui rend la même définition de classe valide:

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; }

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

  1. est-ce bien du code C++ valide, ou juste une bizarrerie du les compilateurs?
  2. S'il s'agit d'un code valide, y a-t-il un paragraphe dans la norme C++ qui traite de cette exception?
  3. si c'est du code valide, pourquoi la première version (sans template) considéré comme invalide? Si le compilateur peut déterminer la deuxième option, je ne vois pas pourquoi il ne serait pas en mesure de comprendre 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; } 

une fois de plus ne parvient pas à compiler.

20
demandé sur Rakete1111 2018-05-06 20:40:10

4 réponses

La réponse réelle peut être \_(ツ)_/, mais c'est probablement actuellement d'accord parce que les modèles sont magiques, mais il peut être plus explicitement ce n'est pas correct dans l'attente de certains autres résolutions de problèmes.

tout D'abord, le principal problème est bien sûr [la classe.mem] / 14:

les éléments de données Non statiques ne doivent pas avoir des types incomplets.

C'est pourquoi votre exemple de non-modèle est mal formé. Toutefois, selon [temp.point] / 4:

pour une spécialisation de modèle de classe, une spécialisation de modèle de membre de classe, ou une spécialisation pour un membre de classe d'un modèle de classe, si la spécialisation est instanciée implicitement parce qu'elle est référencée à l'intérieur d'un autre modèle de spécialisation, si le contexte à partir duquel la spécialisation est référencée dépend d'un paramètre de modèle, et si la spécialisation n'est pas instanciée avant l'instanciation de la en joignant modèle, le point d'instanciation est immédiatement avant le point d'instanciation du modèle d'enveloppe. Autrement, le point d'instanciation d'une telle spécialisation précède immédiatement la déclaration ou la définition de la portée de l'Espace-nom qui se réfère à la spécialisation.

ce Qui suggère que foo_impl<void>::bar est instanciée avantfoo_impl<void>, et donc il est complet au point où le membre de données non statique de type bar est instancié. Alors peut-être que c'est correct.

cependant, le cœur des difficultés de la langue 1626 et 2335 traiter de questions qui ne sont pas-exactement-les-mêmes-mais-tout-à-fait-similaires en ce qui concerne l'exhaustivité et les modèles, et les deux pointent vers le désir de rendre le cas du modèle plus compatible avec le cas du non-modèle.

Qu'est-ce que cela signifie dans l'ensemble? Je ne suis pas sûr.

6
répondu Barry 2018-05-06 19:01:53

je pense que cet exemple est explicitement autorisée par

17.6.1.2 catégories de membres des gabarits de classes [temp.mem.classe]

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. [Note: la classe member doit être définie avant sa première utilisation qui nécessite 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 de la note ]

Cela devrait trop belle:

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
répondu VTT 2018-05-06 17:57:12

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. En extrapolant à partir de cette réponse, Voici les réponses à mes questions initiales:

  1. est - ce bien du code C++ valide, ou juste une bizarrerie des compilateurs? [ c'est un code valide.]
  2. S'il s'agit d'un code valide, y a-t-il un paragraphe dans la norme C++ qui traite de cette exception? [ [temp.point] / 4]
  3. si c'est du code valide, pourquoi la première version (sans template) considéré comme invalide? Si le compilateur peut déterminer la deuxième option, je ne vois pas pourquoi il ne serait pas en mesure de comprendre la première. [ Parce que le C++ est bizarre - il gère les gabarits de classe différemment des classes (vous auriez pu deviner celui-ci). ]

Quelques explications

Ce qui semble se

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

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; }

orientation

par [temp.spec] / 4:

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

donc, l'appel à foo{}.x.value dans la mise en oeuvre originale avec des gabarits se qualifie comme une spécialisation (c'était quelque chose de nouveau pour moi).

à propos de la version avec spécialisation explicite

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

si le contexte de référence de la spécialisation 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
répondu Goran Flegar 2018-05-07 10:19:44

je vais répondre à la troisième partie de votre question-comme IANALL (pas un avocat de langue).

le code est invalide pour la même raison qu'il est invalide d'utiliser une fonction avant qu'elle ne soit déclarée - même si le compilateur peut comprendre ce que la fonction est supposée être en allant plus loin dans la même unité de traduction. Et les cas sont similaires aussi dans le sens que si vous avez juste une déclaration de l'absence de définition, c'est assez bon pour le compilateur, alors là vous arriver à une définition de modèle avant de l'instanciation.

le point est Donc: la norme de langage prescrit que le compilateur ne regarde pas devant pour vous si vous souhaitez définir quelque chose (et un modèle de classe n'est pas une définition d'une classe).

-3
répondu einpoklum 2018-05-06 19:35:33