Clang a-t-il raison de rejeter le code dans lequel la classe imbriquée d'un modèle de classe est définie uniquement via des spécialisations?

17

Étant donné le modèle de classe suivant:

template<typename T>
struct Outer
{
    struct Inner;

    auto f(Inner) -> void;
};

nous définissons Innerséparément pour chaque spécialisation Outer:

template<>
struct Outer<int>::Inner {};

template<>
struct Outer<double>::Inner {};

puis définissez la fonction membre fune fois pour toutes les spécialisations de Outer:

auto Outer<T>::f(Inner) -> void
{

}

mais Clang (9.0.0) se plaint:

error: variable has incomplete type 'Outer::Inner'

auto Outer<T>::f(Inner) -> void

                      ^

Nous pouvons éviter l'erreur de compilation en fournissant également une définition de Innertoutes les autres spécialisations de Outer:

template<typename T>
struct Outer<T>::Inner {};

ou en définissant fséparément pour chaque spécialisation:

template<>
auto Outer<int>::f(Inner) -> void
{

}

template<>
auto Outer<double>::f(Inner) -> void
{

}

GCC et MSVC acceptent le code initial, ce qui pose la question; est-ce un bug Clang ou est-ce la seule implémentation conforme des trois?

Essayez sur Compiler Explorer

invexe
la source
Les spécialisations d'Inner ne sont pas pertinentes, leur suppression ne change pas le résultat de la compilation.
n. «pronoms» m.
@ n.'pronouns'm. Je ne sais pas ce que tu veux dire. L' ajout d'une définition de Innerpour toutes les autres spécialisations et la définition fséparée pour chaque spécialisation résolvent l'erreur de compilation.
invexé
Relisons-le: les supprimer ne change pas le résultat de la compilation . Ne pas ajouter, supprimer. gcc clang
n. «pronoms» m.
@ n.'pronouns'm. Je vois ce que tu veux dire maintenant, mais c'est toujours un commentaire étrange à faire. Le point de ma question était que ce type de rapport Innerest incomplet malgré les définitions de chaque spécialisation Outer. Clairement, ce Innersera (correctement) un type incomplet si vous supprimez sa ou ses définitions.
invexé
"Clairement Inner sera (correctement) un type incomplet si vous supprimez sa (ses) définition (s)." Non, ce n'est pas du tout le cas. Une spécialisation est un modèle complètement séparé et elle n'affecte pas du tout le modèle principal.
n. 'pronoms m.

Réponses:

4

Je crois que Clang a tort de rejeter votre code. Nous devons nous demander, comment votre déclaration de fonction et votre définition se comparent-elles à

auto f(typename T::Inner) -> void;

// ...

template<typename T>
auto Outer<T>::f(typename T::Inner) -> void
{ }

Dans cet exemple, T::Innerest évidemment un type dépendant. Clang ne peut donc pas supposer qu'il est incomplet jusqu'à l'instanciation. Est-ce la même chose dans votre exemple? Je dirais que oui. Car nous avons ceci dans la norme:

[temp.dep.type]

5 Un nom est membre de l'instanciation actuelle s'il est

  • Un nom non qualifié qui, lorsqu'il est recherché, fait référence à au moins un membre d'une classe qui est l'instanciation actuelle ou une classe de base non dépendante de celui-ci. [Remarque: cela ne peut se produire que lors de la recherche d'un nom dans une portée entourée par la définition d'un modèle de classe. - note de fin]
  • ...

Un nom est un membre dépendant de l'instanciation actuelle s'il s'agit d'un membre de l'instanciation actuelle qui, lorsqu'il est recherché, fait référence à au moins un membre d'une classe qui est l'instanciation actuelle.

9 Un type dépend s'il est

  • ...
  • membre d'une spécialisation inconnue,
  • une classe ou une énumération imbriquée qui est un membre dépendant de l'instanciation actuelle,
  • ...

La première puce du paragraphe 9 couvre donc l'affaire typename T::Inner. C'est un type dépendant.

Pendant ce temps, votre cas est couvert par la deuxième puce. Outer::Innerest un nom qui se trouve dans l'instanciation actuelle de Outer, d'ailleurs il se trouve à l'intérieur de Outerlui-même, et non dans une classe de base. Cela en fait un membre dépendant de l'instanciation actuelle. Ce nom fait référence à une classe imbriquée. Ce qui signifie que toutes les conditions de la deuxième puce s'appliquent, ce qui en fait également Outer::Innerun type dépendant!

Puisque nous avons nous-mêmes un type dépendant dans les deux cas, les compilateurs doivent les traiter également comme des types dépendants. Ma conclusion est que GCC et MSVC ont raison.

Conteur - Unslander Monica
la source
Bug signalé . Merci.
invexé