web-dev-qa-db-fra.com

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?

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

template<typename T>
struct Outer
{
    struct Inner;

    auto f(Inner) -> void;
};

nous définissons Inner séparément pour chaque spécialisation de Outer:

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

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

puis définissez la fonction membre f une 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 du compilateur en fournissant également une définition de Inner pour toutes les autres spécialisations de Outer:

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

ou en définissant f sé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

17
invexed

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 à

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

// ...

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

Dans cet exemple, T::Inner est é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 un 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 imbriquée ou une énumération qui est un membre dépendant de l'instanciation actuelle,
  • ...

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

Pendant ce temps, votre cas est couvert par la deuxième puce. Outer::Inner est un nom qui se trouve dans l'instanciation actuelle de Outer, d'ailleurs il se trouve à l'intérieur de Outer lui-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 rend Outer::Inner un type dépendant aussi!

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.