2017-06-14 19 views
22

Z jakiegoś powodu aktualne wersje zarówno GCC, jak i klang nie rozpoznają kowariancji typu powrotu w tym konkretnym scenariuszu. Komunikat o błędzie jest myląca:Typ zwracania kowariantu nie jest rozpoznawany

error: return type of virtual function 'foo' is not covariant with the return 
    type of the function it overrides ('derived *' is not derived from 'base *') 

Oto kod:

class base 
{ 
private: 
    virtual base * foo() = 0; 
}; 

template< class T > 
class foo_default_impl : public virtual base 
{ 
private: 
    T * foo() override { return nullptr; } 
}; 

class derived : public virtual base, private foo_default_impl<derived> 
{ 
}; 

int main() { 
    derived d{}; // error: return type of virtual function 'foo' is not covariant with the return type of the function it overrides ('derived *' is not derived from 'base *') 
    return 0; 
} 
+0

'foo()' musi zwrócić 'foo_default_impl *', a nie 'T *'. –

+2

czy poprzednie wersje skompilowały twój kod? Zastanawiam się, czy to dlatego, że 'wyprowadzony' nie jest jeszcze pełnym typem, gdy jest przekazywane do foo_default_impl: http://eel.is/c++draft/class.derived#class.virtual-8 – marcinj

+0

@KhouriGiordano: Dlaczego? W szczególnym przypadku, który rozważamy, 'T' będzie" wyprowadzony ", a" wyprowadzony "jest publicznie wyprowadzony z' base'. –

Odpowiedz

19

Oto rzeczy. Choć dla nas może się wydawać, że kompilator wie wszystko, co powinien wiedzieć o tych typach, standard mówi inaczej.

[temp.arg.type/2]

... [Uwaga: Szablon typu argumentem może być niekompletny typ. - koniec uwaga]

[basic.types/5]

Klasa, która została zadeklarowana ale nie zdefiniowano, typ wyliczeniowy w pewnych kontekstach ([dcl.enum]) lub tablicą nieznany bound lub niepełnego typu elementów, niecałkowicie określony typ PRZEDMIOT 0,46 niecałkowicie zdefiniowane typy obiektów i odmiany są nieważne niekompletnych ([basic.fu ndamentalny]). Obiekty nie mogą mieć niekompletnego typu.

[class/2]

Klasa nazwa jest umieszczona w zakresie, w jakim jest on zadeklarowanym natychmiast po nazwa-klasa jest postrzegana. Nazwa klasy jest również wprowadzona do zakresu samej klasy; jest to znane jako nazwa klasy wtryskiwanej . Do celów sprawdzania dostępu nazwa klasy wtryskiwanej klasy jest traktowana jak nazwa publiczna. Specyfikator klasy jest zwykle określany jako definicja klasy. Klasa jest uważana za zdefiniowaną po tym, jak nawias zamykający jej specyfikatora klasy został wyświetlony, chociaż jego funkcje składowe na ogół nie są jeszcze zdefiniowane . Opcjonalny atrybut-specyfikator-seq odnosi się do klasy; atrybuty w specyfikatorze atrybutu-seq są następnie uznawane za atrybuty klasy za każdym razem, gdy jest ona nazwana.

Tekst pogrubiony maluje prosty obraz, że dany kompilator traktuje parametr typu T jako niekompletny typ obiektu. To tak, jakby tylko do przodu ogłosił go tak:

class derived; 

Nie można wywnioskować, że ta deklaracja przodu jest klasa pochodzi od base. Nie mogą więc zaakceptować tego jako typu zwrotu współzmiennego w kontekście foo_default_impl.Jak zostało wskazane przez @marcinjin the comments:

[class.virtual/8]

Jeśli typ klasa w kowariantna typ zwracany D :: f różni się od że B :: F, typ klasy, w rodzaju powrotnego D :: f wynosi zakończeniu w momencie zgłoszenia D :: f lub są typu klasy D.

Ponieważ T jest ani kompletny, ani też sam nie może być typem zwrotnym co-wariantowym.

+0

Zgadzam się. Część komunikatu o błędzie _ ("pochodna *" nie pochodzi od "base *") _ sprawiła, że ​​pomyślałem, że kompilator pomylił się z tym kodem. –

+2

Fantastyczne pytanie z fantastyczną odpowiedzią. Dzisiaj się uczę. – cdhowie

+0

Punkt w powyższych komentarzach - że 'sizeof (complete_type)' musi działać - również wyjaśnia, dlaczego 'wyprowadzony' nie może być kompletny w tej linii. Definiujemy bazę "pochodnych", skąd możemy wiedzieć, jak duży jest "pochodny"? – Yakk

Powiązane problemy