A sablon kódban bizonyos típusokat (class
, typename
) vagy egész
számokat, pointereket (int
, ...) fordítási időben cserélhetünk ki. A függvényeket
vagy az osztályokat a szokásos módon írhatjuk meg, azonban előttük, a template
kulcsszó után, kacsacsőrök között meg kell adnunk a sablonparamétereket. A sablonparaméterek
megadása hasonlóképp működik, mint az egyszerű függvényparamétereknél; név, típus sorrendben:
template <typename T> // név és típus, vö. int x
class SomeClass {
/* ... */
};
Ez a sablon elsődleges definíciója (base template). Egyszerre két szerepe is van. Először is,
deklarálja a sablon nevét, és megadja a sablonparaméterek számát és típusát. Jelen esetben egy
SomeClass
nevű osztálysablonról van szó, amelynek egy sablonparamétere van,
egy típus. Másodszor pedig, ez definiálja a sablont, azaz megadja azt a lefordítandó kódot, amelyben
példányosításkor a sablonparaméterek konkrét értékekre cserélődnek.
A sablonoknak létezhet ún. explicit, vagy teljes specializációja (full specialization,
hivatalosan: explicit specialization). Ezzel meg tudunk adni bizonyos sablonparaméter-értékek esetére
egy olyan kódrészletet, amely eltérő működésű, mint az elsődleges
definícióban megadott. A teljes specializációt arról ismerjük meg, hogy a template
kulcsszó
után üres kacsacsőrpár áll <>
, az osztály (vagy függvény) neve után pedig kacsacsőrök
között meg van adva a sablonparaméter konkrét értéke:
template <>
class SomeClass<int> {
/* ... */
};
Az osztály neve után megadott sablonparaméter értékek pontosan olyan számúak és típusúak kell legyenek,
mint az elsődleges definíciónál megadottak. Jelen esetben egyetlen int
típus van megadva,
ami stimmel is számban és típusban is. A template <typename T>
szerint egyetlen paraméter
kell legyen, egy típus neve, az most int
.
Végül pedig, az osztálysablonoknak létezhet részleges specializációja is. Ilyet olyan esetekben használhatunk, amikor a sablonparaméterek bizonyos részhalmazára szeretnénk megadni specializációt. Például ha az elsődleges definíciónál azt mondjuk, az egyik sablonparaméter egy típus, a részleges specializációnál tekinthetjük a típusokon belül a pointerek részhalmazát:
template <typename T>
class SomeClass<T*> {
/* ... */
};
Ez a szintaktika ötvözi az elsődleges definíció és az explicit specializáció elemeit. A template
kulcsszó után meg van adva egy sablonparaméter, <typename T>
, mivel ez még változhat.
Az osztály neve után pedig a <T*>
azt adja meg, hogy hogyan kell a sablonparamétereket
az eredeti, elsődleges definíció paramétereire illeszteni. Mivel az elsődleges definíció azt mondta, hogy egy
darab típusnévből fog állni a sablonparaméterek listája, itt is egy típusnévnek kell lennie; jelen esetben ez
egy pointer.
A részleges specializációnál általában már kevesebb sablonparaméter van, mint az elsődleges definíciónál. Például ha a sablonunk két típus sablonparaméterrel rendelkezik, megadhatunk egy részleges specializációt arra az esetre, amikot két egyező típust ad meg a használó:
template <typename T1, typename T2> // Sablonparaméterek: két típus
class SomeClass {
};
template <typename T>
class SomeClass<T, T> { // Két paraméter, de már csak egy típus
};
Ez nincs szükségszerűen mindig így. Extrém, de egyáltalán nem szokatlan esetben előfordulhat az is, hogy a részleges specializációnak több sablonparamétere van, mint az elsődleges definícióban megadott paraméterek:
template <typename T>
class SomeClass { // Sablonparaméter: egy típus
/* ... */
};
template <typename RET, typename ARG>
class SomeClass<RET (*)(ARG)> { // Egy típus: valamilyen függvénypointer
/* ... */
};
A fenti fogalmazás azonban elég pongyola volt. A specializációnak ugyanúgy egyetlen egy sablonparamétere
van, mint ahogyan az elsődleges definícióban megadott sablon osztálynak: typename T
– RET (*)(ARG)
.
Ez nem is lehet másképp! Csak most a RET (*)(ARG)
függvénypointer típusban több típusnév is cserélgethető, a paraméter
és a visszatérési érték típusa is. Az elsődleges definíció sablonparaméteréhez képest ez még így is részhalmaz,
mert az összes típus halmazának részhalmaza az egyparaméterű függvényekre mutató függvénypointerek halmaza.
Épp a függvények kapcsán fordul elő gyakran, hogy olyan sablont adunk meg, amelynek elsődleges definíciója egyáltalán nincs, csak részleges specializációja. Ez így néz ki:
template <typename FUNCPTR>
class SomeClass; // csak deklaráció
template <typename RET, typename ARG>
class SomeClass<RET (*)(ARG)> {
/* ... */
};
Ezt a sablont csak egyparaméterű függvényre mutató pointerrel lehet példányosítani, semmilyen más típussal. Ha példányosításkor egy függvényre mutató pointer típust adunk meg, a fordító a részleges specializációt fogja használni, és annak a kódjában külön sablonparaméterekként látjuk a visszatérési érték és a paraméter típusát is. Ha bármi mással példányosítunk, akkor a fordító az elsődleges sablont választja ki, amihez viszont nincsen definíció, így a fordítás leáll.
Látszik, hogy az elsődleges definíciók és a specializációk szintaxisa nem túl logikus. Az előbbieknél
a sablonparaméterek számát a template
kulcsszó utáni kacsacsőrökben kell megadni, az utóbbiaknál
pedig már az osztály vagy függvény neve után. Logikusnak tűnhet, hogy ezt egységesítsük:
template <typename T> class MyClass;
template <typename T> class MyClass<T> { /* ... */ }; // HIBÁS!
Ezt azonban nem lehet, nem engedi a szabvány. Ahogyan függvények részleges specializációját sem engedi – azt azért nem, mert összeakadna egy másik nyelvi elemmel, a függvénynevek túlterhelésével.
Az elsődleges definíció hiánya a C++11-es változó argumentumszámú sablonoknál is teljesen szokásos dolog:
template <int... NUMBERS> // tetszőlegesen sok, 0...∞ darab integer
class SomeClass;
template <int HEAD, int... TAIL>
class SomeClass<HEAD, TAIL...> { // specializáció az 1...∞ esetekre
/* ... */
};
template <>
class SomeClass<> { // specializáció a 0 paraméter esetére
/* ... */
}
A nulla elem, és az egytől végtelenig bármennyi elem, közösen kiadják a nullától végtelenig bármennyi elem halmazt. Az első deklarációnak itt is csak annyi a szerepe, hogy megadja, hogyan hívják az osztályt, és milyen sablonparaméterei vannak.
A fenti példákban a template
kulcsszót mindig kacsacsőr követte. Ezt a zárójelpárt akkor is
ki kell írni, ha az teljesen üres (a teljes specializációnál). Ha nem tesszük, akkor a kódrészlet teljesen
mást jelent: explicit példányosítást. A sablonok általában automatikusan példányosodnak, igény szerint, minden
fordítási egységben, ahol használják őket. Ezért kell a definíciókat is a fejlécfájlokba tenni. Ha explicit
példányosítást használunk, akkor egy adott sablonfüggvény, vagy sablonosztály összes tagfüggvénye, példányosodik
a fordítási egységben, akkor is, ha nem használjuk:
template class SomeClass<int>;
Így ha csak néhány konkrét típussal használjuk a sablonunkat, a definíciója áthelyezhető lehet egy
forrásfájlba (.cpp)
a fejlécfájlból (.h
). Ezáltal egy nagy projekt fordítása
gyorsabb lehet.
Ennek éppen az ellenkezőjét jelenti az, ha egy ilyen sor elé még az extern
kulcsszót
is tesszük C++11-ben: az kifejezetten tiltja a sablon példányosítását az adott fordítási egységben. Ahhoz,
hogy egy ilyen program linkelhető legyen, egy másik fordítási egységben példányosítva kell lennie a hivatkozott
sablonnak.
template <typename T> T less(T, T);
| template után van kacsacsőr, deklarált név után nincs:
sablon osztály vagy függvény általános, elsődleges deklarációja: ; vagy definíciója: { ... } .
|
---|---|
template <> int less<int>(int, int);
| template után és név után is van kacsacsőr:
sablon osztály vagy függvény specializációja. Lehet deklaráció ;
és definíció { ... } is.
|
template int less<int>(int, int);
| template után nincs, de a név után van kacsacsőr:
explicit példányosítás, azaz példányosítás kérése akkor is, ha
használattal az adott fordítási egységben nem találkozik a fordító.
|
extern template int less<int>(int, int);
| C++11, példányosítás tiltása az adott fordítási egységben, mivel tudjuk, hogy máshol biztosan példányosodik a sablon, és linkelhető lesz a program. |
- Herb Sutter: Why Not Specialize Function Templates? – miért nincs részleges specializáció függvényekhez?