Verursacht eine explizite Instanziierungsdeklaration einer Mitgliedsfunktion einer Klassenvorlage eine Instanziierung der Klassenvorlage?

Nov 20 2020

[dcl.spec.auto] / 14 Staaten [ Schwerpunkt Mine]:

Eine explizite Instanziierungsdeklaration bewirkt nicht die Instanziierung einer Entität, die unter Verwendung eines Platzhaltertyps deklariert wurde , verhindert jedoch auch nicht, dass diese Entität nach Bedarf instanziiert wird, um ihren Typ zu bestimmen. [  Beispiel:

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

 -  Beispiel beenden  ]

und [temp.explicit] / 11 Staaten [ Schwerpunkt Mine]:

Eine Entität , die Gegenstand einer expliziten Instanziierungsdeklaration ist und die auch auf eine Weise verwendet wird, die andernfalls eine implizite Instanziierung in der Übersetzungseinheit verursachen würde, muss Gegenstand einer expliziten Instanziierungsdefinition irgendwo im Programm sein. Andernfalls ist das Programm fehlerhaft, keine Diagnose erforderlich.

Betrachten Sie nun das folgende Programm:

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() {}

Das ist wohlgeformt; [temp.explicit] / 11 gilt weder als Elementfunktion von Klassenvorlagen-Spezialisierungsentitäten Foo<void>::foo()noch Foo<int>::foo()wird sie in einer Weise verwendet, die andernfalls eine implizite Instanziierung gemäß [dcl.spec.auto] / 14 (1) verursachen würde .

Überlegen Sie nun, ob wir eine Friend-Funktion bei ihrer Friend-Deklaration in der Klassenvorlage definiert haben Foo:

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

Wenn mehr als eine Spezialisierung von Fooin derselben Übersetzungseinheit instanziiert wird, wird [basic.def.odr] / 1 verletzt:

Keine Übersetzungseinheit darf mehr als eine Definition einer Variablen, Funktion, eines Klassentyps, eines Aufzählungstyps oder einer Vorlage enthalten.

als würde der Freund für jede Instanziierung, die instanziiert wird bar(), neu definiert (2) .

Gemäß dem obigen Argument sollten die expliziten Instanziierungsdeklarationen der Spezialisierungen mit zwei Mitgliedsfunktionen (der Klassenvorlage) nicht zu einer Instanziierung der zugehörigen Klassenvorlage (gemäß [dcl.spec.auto] / 14 ) führen, was das folgende Programm bedeutet sollte wohl auch wohlgeformt sein:

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() {}

Sowohl Clang (10.0.0) als auch GCC (10.1.0) lehnen das Programm (C ++ 14, C ++ 17, C ++ 2a) mit dem void bar()Fehler "Neudefinition von " ab:

Clang

Fehler: Neudefinition von bar

Hinweis: Zur Instanziierung der Foo<int>hier angeforderten Vorlagenklasse :extern template const auto& Foo<int>::foo();

GCC

In Instanziierung von struct Foo<int>:

Fehler: Neudefinition von void bar()

Aber ich habe nie darum gebeten (oder, wie gesagt, diese Spezialisierungen so verwendet), dass die Foo<int>oder Foo<void>Spezialisierungen instanziiert werden sollen.

Also zur Frage:

  • Ist das Programm (mit dem Freund) oben wohlgeformt oder sind die Compiler korrekt, um die Klassenvorlagenspezialisierungen zu instanziieren und das Programm anschließend abzulehnen?

(1) Beachten Sie, dass dieselbe Frage (und dasselbe Compilerverhalten) auch dann gilt, wenn sie foo()nicht mit einem Platzhaltertyp deklariert wurde. Dann könnten wir jedoch nicht auf die Aussage von [dcl.spec.auto] / 14 zurückgreifen , sondern auf uns muss möglicherweise nicht.

(2) Da Freunde, die in ihrer Freundeserklärung definiert wurden, inline sind, können wir tatsächlich verschiedene Spezialisierungen in verschiedenen Übersetzungseinheiten instanziieren und dennoch die ODR respektieren, aber dies ist in dieser Diskussion nicht relevant.

Antworten

1 DavisHerring Nov 20 2020 at 10:04

Das Argument , dass die Klassenvorlage instanziiert werden muss , ist die Erklärung Anpassung benötigt Dinge über die Klasse wissen , die deutlich Instanziierung erfordern. Betrachten Sie das vereinfachte Beispiel

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

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

Um zu wissen, ob die Elementfunktion vorhanden ist, müssen wir die Deklaration in der Klassenvorlage instanziieren. Dies ist im Allgemeinen nicht möglich, ohne die gesamte Klasse zu instanziieren: Der Parametertyp kann von anderen Deklarationen in der Klassenvorlage abhängen , und wir können dies auch Sie müssen mehrere Überladungen berücksichtigen oder sogar Vorlagenargumente abziehen, um zu entscheiden, was fgemeint ist. Man kann argumentieren, dass eine Instanziierung nur dann stattfinden sollte, wenn tatsächlich eine dieser Situationen vorliegt, die in das Gebiet von CWG2 abwandert (wo eine Instanziierung offensichtlich unmöglich ist), aber die Idee ist, dass eine Instanziierung im Prinzip notwendig ist, um über solche Fragen zu entscheiden , weil wir dies einfach nicht tun Versuchen Sie zuerst , die Vorlage selbst zu untersuchen.