Czy jawna deklaracja instancji funkcji składowej szablonu klasy powoduje utworzenie instancji szablonu klasy?
[dcl.spec.auto] / 14 stanów [ podkreślenie moje]:
Jawna deklaracja instancji nie powoduje utworzenia instancji jednostki zadeklarowanej przy użyciu typu symbolu zastępczego , ale nie zapobiega również utworzeniu instancji tej jednostki w razie potrzeby w celu określenia jej typu. [ Przykład:
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
- przykład końca ]
i [temp.explicit] / 11 stany [ nacisk kopalni]
Jednostka, która jest przedmiotem jawnej deklaracji instancji i która jest również używana w sposób, który w innym przypadku spowodowałaby niejawną instancję w jednostce tłumaczeniowej , będzie przedmiotem jawnej definicji instancji gdzieś w programie; w przeciwnym razie program jest źle sformułowany, nie jest wymagana diagnostyka.
Rozważmy teraz następujący program:
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() {}
To jest dobrze uformowane; [temp.explicit] / 11 nie ma zastosowania jako funkcja składowa jednostek specjalizujących się w szablonach klas Foo<void>::foo()
ani nie Foo<int>::foo()
są używane w sposób, który w przeciwnym razie spowodowałby niejawną instancję, zgodnie z [dcl.spec.auto] / 14 (1) .
Rozważmy teraz, czy zdefiniowaliśmy funkcję zaprzyjaźnioną w jej deklaracji znajomego w szablonie klasy Foo
:
template <class T>
struct Foo {
static const auto& foo() { static T t; return t; }
friend void bar() { }
};
void bar();
Jeśli Foo
w tej samej jednostce tłumaczeniowej zostanie utworzona instancja więcej niż jednej specjalizacji , [basic.def.odr] / 1 zostanie naruszony:
Żadna jednostka tłumaczeniowa nie może zawierać więcej niż jednej definicji dowolnej zmiennej, funkcji, typu klasy, typu wyliczenia lub szablonu.
ponieważ przyjaciel bar()
zostałby ponownie zdefiniowany (2) dla każdej tworzonej instancji specjalizacji.
Zgodnie z powyższym argumentem, jawne deklaracje instancji dwóch specjalizacji funkcji składowych (szablonu klasy) nie powinny prowadzić do jakiejkolwiek instancji powiązanego szablonu klasy (zgodnie z [dcl.spec.auto] / 14 ), co oznacza następujący program powinien być również dobrze sformułowany:
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() {}
Jednak zarówno Clang (10.0.0), jak i GCC (10.1.0) odrzucają program (C ++ 14, C ++ 17, C ++ 2a) z void bar()
błędem „redefinicja ”:
Szczęk
błąd: przedefiniowanie
bar
uwaga: w instancji klasy szablonu
Foo<int>
żądanej tutaj:extern template const auto& Foo<int>::foo();
GCC
W instancji
struct Foo<int>
:błąd: przedefiniowanie
void bar()
Ale nigdy nie prosiłem (ani, oczywiście, nie korzystałem z tych specjalizacji w taki sposób, że) mają zostać utworzone instancje Foo<int>
lub Foo<void>
specjalizacje.
Stąd pytanie:
- Czy program (z przyjacielem) powyżej jest dobrze sformułowany, czy też kompilatory są poprawne, aby utworzyć instancję specjalizacji szablonu klasy, a następnie odrzucić program?
(1) Zauważ, że to samo pytanie (i zachowanie kompilatora) ma zastosowanie, nawet jeśli foo()
nie jest zadeklarowane przy użyciu typu symbolu zastępczego, ale wtedy nie moglibyśmy wrócić do jawności [dcl.spec.auto] / 14 , ale może nie być konieczne.
(2) Ponieważ znajomi zdefiniowani w deklaracji znajomego są wbudowani, możemy faktycznie tworzyć instancje różnych specjalizacji w różnych jednostkach tłumaczeniowych i nadal przestrzegać ODR, ale nie ma to znaczenia w tej dyskusji.
Odpowiedzi
Argumentem, że szablon klasy musi zostać utworzony, jest to, że dopasowanie deklaracji może wymagać informacji o klasie, która po prostu wymaga wystąpienia. Rozważmy uproszczony przykład
template<class T>
struct A {void f(T) {}};
extern template void A<int>::f(int);
Aby wiedzieć, czy funkcja członkowska istnieje, musimy utworzyć instancję deklaracji w szablonie klasy, a nie możemy tego zrobić w ogóle bez tworzenia instancji całej klasy: typ parametru może zależeć od innych deklaracji w szablonie klasy i możemy trzeba wziąć pod uwagę wiele przeciążeń, a nawet przeprowadzić dedukcję argumentów szablonu, aby zdecydować, o co f
chodzi. Można argumentować, że tworzenie instancji powinno mieć miejsce tylko wtedy, gdy faktycznie występuje jedna z tych sytuacji, która błąka się na terytorium CWG2 (gdzie instancja jest oczywiście niemożliwa), ale chodzi o to, że instancje są w zasadzie konieczne do decydowania o takich pytaniach, ponieważ po prostu nie spróbuj najpierw zbadać sam szablon.