A typename kulcsszó
Czirkos Zoltán · 2022.06.21.
Templatelt osztályból öröklődés rejtelmei
Mit jelent az alábbi kód?
void f() {
A::B * C;
}
Valójában a kérdésnek két megoldása lehet:
A
osztálybeliB
belső típusú objektumra mutató pointer, amelynek a neveC
. Tehát a*C
kifejezéssel egyA::B
típusú objektumot érnénk el. Konkrét példa:string::iterator * p
.A
osztálybeliB
nevű statikus változó, amit összeszorzunk C-vel.
A félreérthetőséget az okozza, hogy a belső típust és a statikus változót
ugyanazzal a szintaxissal kell elérni; mindkettőt a ::
scope operator
segítségével tudjuk hivatkozni.
A fentiekkel alapesetben nincsen gond. Bár a fordítóprogramok tervezésekor nehézséget jelent, hogy
a sor jelentése kontextusfüggő – bele kell néznie az A
osztályba,
hogy kiderüjön a kódsor jelentése –, az A
osztály vizsgálata után egyértelmű
a kód.
Kivétel persze akkor, ha sablon kódban vagyunk:
template <typename A>
void f() {
A::B * C;
}
Jelen pillanatban ugyanis a kódrészlet mindkét verzióvá „átváltozhatna”. Ha a sablonparaméter
egy olyan osztály, amelyikben B
belső típus, akkor C
-t egy pointerként
lehetne értelmezni:
class Egyik {
public:
class B {};
};
f<Egyik>();
/* A keletkező specializáció: */
template <>
void f<Egyik>() {
Egyik::B * C;
}
Viszont ha egy másik osztállyal példányosítjuk a sablont, amelyben a B
egy statikus
változó, akkor ez ismét szorzásnak tűnik:
class Masik {
public:
static int B;
};
f<Masik>();
/* A keletkező specializáció: */
template <>
void f<Masik>() {
Masik::B * C;
}
Hogyan oldható fel ez a kétértelműség? Úgy, hogy a kódban jeleznünk kell a fordító számára,
hogy melyikről van szó; vagyis a ::
scope operator használata esetén megjelöljük, hogy
statikus változóra vagy belső típusra kell gondolnia. A szabály egyszerű:
- Ha nem mondunk semmit, statikus változónak tekinti.
- Viszont ha belső típusra szeretnénk hivatkozni, akkor elé kell írnunk,
hogy
typename
.
template <typename A>
void f1() {
A::B * C; // statikus változózó
}
template <typename A>
void f2() {
typename A::B * C; // belső típus
}
A::B
-t ilyenkor függő névnek (dependent name) nevezzük; a jelentése ugyanis függ
az A
sablonparamétertől. A függés lehet közvetlen, vagyis a sablonparaméter maga lehet
az az osztály, amelyből egy belső típust felhasználunk:
template <typename CONTAINER>
void f() {
typename CONTAINER::iterator * ptr;
}
De lehet közvetett is, például:
template <typename T>
void f() {
typename std::vector<T>::iterator * ptr;
}
A probléma itt is fennáll, hiszen amíg a T
nem ismert, addig nem lehet tudni,
a vektornak melyik specializációjával dolgozunk. Ne feledjük, csak számunkra egyértelmű ennek
a kódnak a jelentése: a vektor, iterátor, ptr nevek elárulják, mire gondolunk. A fordító
számára viszont ezek nem jelentenek semmit, az csak ennyit lát:
template <typename IZEBIGYO>
void f() {
typename VALAMI::AKARMI<T>::BARMI * BLABLA;
}
Ez egész problémakör egyébként teljesen más kontextusokban is előkerülhet:
template <typename T>
class Alap {
public:
int tagvaltozo;
class BelsoTipus {
...
};
...
};
template <typename U>
class Leszarmazott: public Alap<U> {
void fv1() {
std::cout << tagvaltozo; // 1
}
void fv2() {
BelsoTipus x; // 2
}
};
Az 1-essel jelölt helyen nem lehet tudni, hogy tagváltozóról,
vagy globális változóról van szó. Ha Alap<U>
rendelkezik ilyen nevű
attribútummal, akkor ez lehetne az is. De ha nem – márpedig létezhet olyan specializációja,
amelyiknek nincs ilyenje –, akkor ez egy globális változó. (Ha ez a helyzet, és tényleg
van ilyen nevű globális változó, akkor a fordító értelmezni tudja majd a kódot!) Egyértelművé
tehetjük ezt az ősosztály megjelölésével, vagy a this
kiírásával:
void fv1() {
std::cout << Alap<U>::tagvaltozo;
}
void fv1() {
std::cout << this->tagvaltozo;
}
A 2-essel jelölt helyen ugyanez a helyzet: nem lehet tudni, hogy az ősosztály belső típusáról van szó, vagy egy globálisan létező típusról. Alapértelmezetten globális típusról, tehát a belső osztály hivatkozását kell egyértelműsíteni:
void fv2() {
typename Alap<U>::BelsoTipus x;
}
Mivel ilyenkor függő nevet hivatkozunk (Alap<U>
függ az U
sablonparamétertől),
ezért a typename
kulcsszó is előkerül. Amelyik fordító enélkül is lefordítja a kódot,
az nem követi a szabványt.
- Why am I getting errors when my template-derived-class uses a nested type it inherits from its template-base-class?
- Why am I getting errors when my template-derived-class uses a member it inherits from its template-base-class?
- Can the previous problem hurt me silently? Is it possible that the compiler will silently generate the wrong code?