Gdyby poniższe klasy nie były szablonami, po prostu mógłbym je mieć x
w derived
klasie. Jednak z poniższym kodzie I trzeba użytku this->x
. Czemu?
template <typename T>
class base {
protected:
int x;
};
template <typename T>
class derived : public base<T> {
public:
int f() { return this->x; }
};
int main() {
derived<int> d;
d.f();
return 0;
}
x
zthis->
, a mianowicie: 1) używać prefiksubase<T>::x
, 2) dodać oświadczenieusing base<T>::x
, 3) użyj globalnego przełącznik kompilatora, który umożliwia tryb dopuszczający. Zalety i wady tych rozwiązań opisano w stackoverflow.com/questions/50321788/...Odpowiedzi:
Krótka odpowiedź: aby utworzyć
x
zależną nazwę, aby wyszukiwanie zostało odroczone do momentu poznania parametru szablonu.Długa odpowiedź: gdy kompilator zobaczy szablon, powinien natychmiast wykonać pewne kontrole, nie widząc parametru szablonu. Inne są odraczane do momentu poznania parametru. Nazywa się to kompilacją dwufazową, a MSVC tego nie robi, ale jest wymagana przez standard i implementowana przez inne główne kompilatory. Jeśli chcesz, kompilator musi skompilować szablon, gdy tylko go zobaczy (do pewnego rodzaju wewnętrznej reprezentacji drzewa parsowania) i odłożyć kompilację na później.
Kontrole przeprowadzane na samym szablonie, a nie na poszczególnych jego instancjach, wymagają, aby kompilator mógł rozwiązać gramatykę kodu w szablonie.
W C ++ (i C), aby rozwiązać gramatykę kodu, czasami trzeba wiedzieć, czy coś jest typem, czy nie. Na przykład:
jeśli A jest typem, który deklaruje wskaźnik (bez efektu innego niż cień globalny
x
). Jeśli A jest obiektem, jest to zwielokrotnianie (a blokowanie przeciążania przez operatora jest nielegalne, przypisywanie do wartości). Jeśli jest niepoprawny, błąd ten musi zostać zdiagnozowany w fazie 1 , zgodnie ze standardem jest zdefiniowany jako błąd w szablonie , a nie w określonej instancji. Nawet jeśli szablon nigdy nie jest tworzony, jeśli A jestint
a, to powyższy kod jest źle sformułowany i musi zostać zdiagnozowany, tak jak by to było, gdyby wfoo
ogóle nie był szablonem, ale zwykłą funkcją.Teraz standard mówi, że nazwy, które nie są zależne od parametrów szablonu, muszą być rozpoznawalne w fazie 1.
A
tutaj nie jest nazwa zależna, odnosi się do tej samej rzeczy bez względu na typT
. Dlatego należy go zdefiniować przed zdefiniowaniem szablonu, aby można go znaleźć i sprawdzić w fazie 1.T::A
byłoby nazwą zależną od T. W fazie 1 prawdopodobnie nie wiemy, czy jest to typ, czy nie. Typ, który ostatecznie zostanie użyty jakoT
instancja, najprawdopodobniej nie jest jeszcze zdefiniowany, a nawet jeśli tak, nie wiemy, który typ zostanie użyty jako parametr szablonu. Ale musimy rozwiązać gramatykę, aby wykonać nasze cenne testy w fazie 1 pod kątem źle sformułowanych szablonów. Zatem standard ma regułę dla nazw zależnych - kompilator musi założyć, że nie są typami, chyba że kwalifikuje siętypename
do określenia, że są typami lub są używane w pewnych jednoznacznych kontekstach. Na przykład, wtemplate <typename T> struct Foo : T::A {};
,T::A
stosuje się jako bazową, a więc jednoznacznie typu. JeśliFoo
jest tworzone z jakiegoś typu, który ma członka danychA
zamiast zagnieżdżonego typu A jest to błąd w kodzie tworzącym instancję (faza 2), a nie błąd w szablonie (faza 1).Ale co z szablonem klasy z zależną klasą bazową?
Czy nazwa zależna, czy nie? W przypadku klas podstawowych każda nazwa może pojawić się w klasie podstawowej. Moglibyśmy więc powiedzieć, że A jest nazwą zależną i traktować ją jako nietypową. Miałoby to niepożądany efekt, że każda nazwa w Foo jest zależna, a zatem każdy typ użyty w Foo (oprócz typów wbudowanych) musi zostać zakwalifikowany. Wewnątrz Foo musisz napisać:
ponieważ
std::string
byłaby to nazwa zależna, a zatem zakłada się, że nie jest typem, chyba że określono inaczej. Auć!Drugi problem z zezwoleniem na preferowany kod (
return x;
) polega na tym, że nawet jeśliBar
został wcześniej zdefiniowanyFoo
ix
nie jest członkiem tej definicji, ktoś może później zdefiniować specjalizacjęBar
dla jakiegoś typuBaz
, na przykład taką,Bar<Baz>
która ma element danychx
, a następnie utworzyć instancjęFoo<Baz>
. Tak więc w tej instancji szablon zwróciłby element danych zamiast zwracać wartość globalnąx
. Lub odwrotnie, jeśli podstawowa definicja szablonuBar
miałabyx
, mogliby zdefiniować specjalizację bez niej, a Twój szablon szukałby globalnej wartości,x
do której mógłby wrócićFoo<Baz>
. Myślę, że uznano to za równie zaskakujące i niepokojące jak problem, który masz, ale to po cichu zaskakujące, w przeciwieństwie do rzucania zaskakującego błędu.Aby uniknąć tych problemów, obowiązujący standard mówi, że zależne klasy podstawowe szablonów klas po prostu nie są brane pod uwagę przy wyszukiwaniu, chyba że jest to wyraźnie wymagane. To powstrzymuje wszystko od uzależnienia tylko dlatego, że można je znaleźć w zależnej bazie. Ma również niepożądany efekt, który widzisz - musisz zakwalifikować rzeczy z klasy podstawowej, w przeciwnym razie nie zostanie znaleziony. Istnieją trzy popularne sposoby
A
uzależnienia:using Bar<T>::A;
w klasie -A
teraz odnosi się do czegośBar<T>
, co jest zależne.Bar<T>::A *x = 0;
w miejscu użycia - Znowu,A
zdecydowanie jest wBar<T>
. Jest to mnożenie, ponieważtypename
nie zostało użyte, więc prawdopodobnie zły przykład, ale będziemy musieli poczekać do wystąpienia, aby dowiedzieć się, czyoperator*(Bar<T>::A, x)
zwraca wartość. Kto wie, może to robi ...this->A;
w miejscu użycia -A
jest członkiem, więc jeśli go nie maFoo
, musi należeć do klasy podstawowej, ponownie standard mówi, że to uzależnia.Kompilacja dwufazowa jest skomplikowana i trudna, i wprowadza pewne zaskakujące wymagania dotyczące dodatkowego języka w kodzie. Ale raczej, jak demokracja, jest to prawdopodobnie najgorszy możliwy sposób, oprócz wszystkich innych.
Można zasadnie argumentować, że w twoim przykładzie
return x;
nie ma sensu, jeślix
jest to typ zagnieżdżony w klasie bazowej, więc język powinien (a) powiedzieć, że jest to nazwa zależna i (2) traktować go jako nietypowy, i twój kod działałby bezthis->
. W pewnym stopniu jesteś ofiarą szkód ubocznych spowodowanych rozwiązaniem problemu, który nie ma zastosowania w twoim przypadku, ale wciąż istnieje problem, że twoja klasa podstawowa potencjalnie wprowadza pod tobą nazwy, które cieniują globale, lub nie ma imion, o których myślałeś mieli, a zamiast tego znaleziono globalną istotę.Można również argumentować, że wartość domyślna powinna być odwrotna dla nazw zależnych (zakładając, że typ nie jest określony jako obiekt), lub że wartość domyślna powinna być bardziej wrażliwa na kontekst (w
std::string s = "";
,std::string
może być odczytana jako typ, ponieważ nic innego nie czyni gramatyki sens, choćstd::string *s = 0;
jest niejednoznaczny). Ponownie nie wiem dokładnie, w jaki sposób uzgodniono zasady. Domyślam się, że liczba stron tekstu, które byłyby wymagane, złagodziła potrzebę stworzenia wielu szczegółowych reguł, dla których konteksty przyjmują typ, a które nie są typem.źródło
-fpermissive
lub podobne, tak, jest to możliwe. Nie znam szczegółów jego implementacji, ale kompilator musix
odkładać rozpoznawanie, dopóki nie pozna rzeczywistej podstawowej klasy tymczasowejT
. Tak więc, w zasadzie w trybie niedozwolonym, może on rejestrować fakt, że go odroczył, odracza go, wykona wyszukiwanie, gdy już to zrobiT
, a gdy wyszukiwanie zakończy się powodzeniem, wyda sugerowany tekst. Byłaby to bardzo trafna sugestia, gdyby została wydana tylko w przypadkach, w których działa: szanse, że użytkownik miał na myśli innyx
z jeszcze innego zakresu, są bardzo małe!(Oryginalna odpowiedź z 10 stycznia 2011 r.)
Myślę, że znalazłem odpowiedź: problem GCC: użycie członka klasy podstawowej, która zależy od argumentu szablonu . Odpowiedź nie jest specyficzna dla gcc.
Aktualizacja: W odpowiedzi na komentarz mmichaela z projektu N3337 standardu C ++ 11:
Czy „ponieważ standard tak mówi” liczy się jako odpowiedź, nie wiem. Możemy teraz zapytać, dlaczego standard tak nakazuje, ale jak podkreślają doskonała odpowiedź Steve'a Jessopa i inni, odpowiedź na to ostatnie pytanie jest dość długa i dyskusyjna. Niestety, jeśli chodzi o standard C ++, często jest prawie niemożliwe, aby udzielić krótkiego i niezależnego wyjaśnienia, dlaczego standard coś nakazuje; dotyczy to również tego ostatniego pytania.
źródło
x
Jest ukryty podczas spadku. Możesz odkryć za pomocą:źródło