Une déclaration d'instanciation explicite d'une fonction membre d'un modèle de classe provoque-t-elle une instanciation du modèle de classe?

Nov 20 2020

[dcl.spec.auto] / 14 états [c'est moi qui souligne ]:

Une déclaration d'instanciation explicite ne provoque pas l'instanciation d'une entité déclarée à l'aide d'un type d'espace réservé , mais elle n'empêche pas non plus que cette entité soit instanciée selon les besoins pour déterminer son type. [  Exemple:

template <typename T> auto f(T t) { return t; }
extern template auto f(int);    // does not instantiate f<int>
int (*p)(int) = f;              // instantiates f<int> to determine its return type, but an explicit
                                // instantiation definition is still required somewhere in the program

 -  fin d'exemple  ]

et [temp.explicit] / 11 états [c'est moi qui souligne ]:

Une entité qui fait l'objet d'une déclaration d'instanciation explicite et qui est également utilisée d'une manière qui provoquerait autrement une instanciation implicite dans l'unité de traduction doit faire l'objet d'une définition d'instanciation explicite quelque part dans le programme; sinon le programme est mal formé, aucun diagnostic n'est requis.

Maintenant, considérez le programme suivant:

template <class T>
struct Foo {
    static const auto& foo() { static T t; return t; }
};

// explicit instantiation declarations
extern template const auto& Foo<void>::foo();
extern template const auto& Foo<int>::foo();

int main() {}

Ceci est bien formé; [temp.explicit] / 11 ne s'applique pas en tant que fonction membre des entités de spécialisation de modèle de classe Foo<void>::foo()ni Foo<int>::foo()n'est utilisé d'une manière qui provoquerait autrement une instanciation implicite, selon [dcl.spec.auto] / 14 (1) .

Maintenant, considérez si nous avons défini une fonction friend lors de sa déclaration friend dans le modèle de classe Foo:

template <class T>
struct Foo {
    static const auto& foo() { static T t; return t; }
    friend void bar() { }
};
void bar();

Si plus d'une spécialisation de Fooest instanciée dans la même unité de traduction, [basic.def.odr] / 1 sera violé:

Aucune unité de traduction ne doit contenir plus d'une définition d'une variable, d'une fonction, d'un type de classe, d'un type d'énumération ou d'un modèle.

comme l'ami bar()serait redéfini (2) pour chaque spécialisation instanciée.

Selon l'argument ci-dessus, les déclarations d'instanciation explicites des deux spécialisations de fonction membre (du modèle de classe) ne doivent conduire à aucune instanciation du modèle de classe associé (selon [dcl.spec.auto] / 14 ), c'est-à-dire le programme suivant devrait également être bien formé:

template <class T>
struct Foo {
    static const auto& foo() { static T t; return t; }
    friend void bar() { }
};
void bar();

extern template const auto& Foo<void>::foo();
extern template const auto& Foo<int>::foo();

int main() {}

Cependant, Clang (10.0.0) et GCC (10.1.0) rejettent le programme (C ++ 14, C ++ 17, C ++ 2a) avec une void bar()erreur "redéfinition de ":

Bruit

erreur: redéfinition de bar

note: dans l'instanciation de la classe de modèle Foo<int>demandée ici:extern template const auto& Foo<int>::foo();

GCC

En instanciation de struct Foo<int>:

erreur: redéfinition de void bar()

Mais je n'ai jamais demandé (ou, afaict, utilisé ces spécialisations d'une manière telle que) la Foo<int>ou les Foo<void>spécialisations (doivent) être instanciées.

Ainsi à la question:

  • Le programme (avec l'ami) ci-dessus est-il bien formé, ou les compilateurs ont-ils raison d'instancier les spécialisations de modèle de classe et de rejeter ensuite le programme?

(1) Notez que la même question (et le comportement du compilateur) s'applique même si elle foo()n'est pas déclarée en utilisant un type d'espace réservé, mais alors nous ne serions pas en mesure de revenir sur l'explicitation de [dcl.spec.auto] / 14 , mais nous peut-être pas besoin.

(2) Comme les amis définis lors de leur déclaration d'ami sont en ligne, nous pouvons en fait instancier différentes spécialisations dans différentes unités de traduction tout en respectant l'ODR, mais cela n'est pas pertinent dans cette discussion.

Réponses

1 DavisHerring Nov 20 2020 at 10:04

L'argument selon lequel le modèle de classe doit être instancié est que la correspondance de déclaration peut avoir besoin de connaître des choses sur la classe qui nécessitent clairement une instanciation. Prenons l'exemple simplifié

template<class T>
struct A {void f(T) {}};

extern template void A<int>::f(int);

Pour savoir si la fonction membre existe, nous devons instancier la déclaration dans le modèle de classe, et nous ne pouvons pas faire cela en général sans instancier toute la classe: le type de paramètre pourrait dépendre de toute autre déclaration dans le modèle de classe, et nous pourrions Il faut tenir compte de plusieurs surcharges ou même faire une déduction d'argument de modèle pour décider de ce que l' fon veut dire. On peut affirmer que l'instanciation ne devrait se produire que si l'une de ces situations existe réellement, ce qui s'égare dans le territoire du CWG2 (où l'instanciation est évidemment impossible), mais l'idée est que l'instanciation est en principe nécessaire pour décider de telles questions parce que nous ne essayez d'abord d' examiner le modèle lui-même.