É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?
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.