Biorąc pod uwagę następujący szablon klasy:
template<typename T>
struct Outer
{
struct Inner;
auto f(Inner) -> void;
};
definiujemy Inner
osobno dla każdej specjalizacji Outer
:
template<>
struct Outer<int>::Inner {};
template<>
struct Outer<double>::Inner {};
a następnie raz zdefiniuj funkcję członka f
dla wszystkich specjalizacji Outer
:
auto Outer<T>::f(Inner) -> void
{
}
ale Clang (9.0.0) narzeka:
error: variable has incomplete type 'Outer::Inner'
auto Outer<T>::f(Inner) -> void
^
Możemy uniknąć błędu kompilatora, podając również definicję Inner
wszystkich innych specjalizacji Outer
:
template<typename T>
struct Outer<T>::Inner {};
lub definiując f
osobno dla każdej specjalizacji:
template<>
auto Outer<int>::f(Inner) -> void
{
}
template<>
auto Outer<double>::f(Inner) -> void
{
}
Zarówno GCC, jak i MSVC akceptują kod początkowy, co nasuwa pytanie; czy jest to błąd Clanga, czy jest to jedyna zgodna implementacja spośród wszystkich trzech?
Inner
dla wszystkich innych specjalizacji, jak i zdefiniowanief
osobno dla każdej specjalizacji rozwiązuje błąd kompilacji.Inner
jest zgłaszany jako niekompletny typ, pomimo definicji dla każdej specjalizacjiOuter
. WyraźnieInner
będzie (poprawnie) niekompletny, jeśli usuniesz jego definicje.Odpowiedzi:
Uważam, że Clang nie zgadza się z odrzuceniem twojego kodu. Musimy zadać sobie pytanie, jak wypada twoja deklaracja funkcji i definicja
W tym przykładzie
T::Inner
jest oczywiście typ zależny. Dlatego Clang może nie założyć, że jest niekompletny do momentu utworzenia instancji. Czy to samo dotyczy twojego przykładu? Tak bym powiedział. Ponieważ mamy to w standardzie:Zatem pierwszy punkt w paragrafie 9 dotyczy sprawy
typename T::Inner
. To jest typ zależny.Tymczasem twoja sprawa jest objęta drugim pociskiem.
Outer::Inner
to nazwa, która znajduje się w bieżącej instancjiOuter
, ponadto znajduje się wOuter
sobie, a nie w klasie bazowej. To czyni go zależnym członkiem bieżącej instancji. Ta nazwa odnosi się do zagnieżdżonej klasy. Co oznacza, że obowiązują wszystkie warunki zawarte w drugim pocisku, a zatemOuter::Inner
również typ zależny!Ponieważ w obu przypadkach mamy typ zależny, kompilatory powinny traktować je jednakowo jako typy zależne. Mój wniosek jest taki, że GCC i MSVC mają rację.
źródło