Zaawansowane CPP/Wykład 3: Szablony II: Różnice pomiędzy wersjami
Matiunreal (dyskusja | edycje) |
m Zastępowanie tekstu - "<div class="thumb"><div style="width:(.*);"> <flash>file=(.*)\.swf\|width=(.*)\|height=(.*)<\/flash> <div\.thumbcaption>(.*)<\/div> <\/div><\/div>" na "$3x$4px|thumb|center|$5" |
||
(Nie pokazano 74 wersji utworzonych przez 7 użytkowników) | |||
Linia 1: | Linia 1: | ||
==Wprowadzenie== | ==Wprowadzenie== | ||
Mechanizm szablonów jest bardzo użyteczny ale może się okazać że kod | Mechanizm szablonów jest bardzo użyteczny ale może się okazać, że kod | ||
ogólny który szablon implementuje nie nadaje się do stosowania w | ogólny, który szablon implementuje, nie nadaje się do stosowania w | ||
każdym przypadku. W tej sytuacji mamy do dyspozycji dodatkowe | każdym przypadku. W tej sytuacji mamy do dyspozycji dodatkowe | ||
własności implementacji szablonów w C++: przeciążanie i specjalizację. | własności implementacji szablonów w C++: przeciążanie i specjalizację. | ||
Linia 15: | Linia 15: | ||
elementu w tablicy: | elementu w tablicy: | ||
<Source> | |||
template<typename T> T max(T *data,size_t n) { | |||
T _max = data[0]; | |||
for(size_t i=0;i<n;i++) | |||
if(data[i]>_max) _max=data[i]; | |||
return _max; | |||
} | |||
</Source> | |||
Oba szablony: powyższy i wcześniej zdefiniowany | Oba szablony: powyższy i wcześniej zdefiniowany | ||
<Source> | |||
template<typename T> T max(T a,T b) {return (a>b)?a:b;}; | |||
</Source> | |||
{{przyklad|3.1||}} | {{kotwica|przyklad_3.1|}}{{przyklad|3.1||}} | ||
mogą ze sobą współistnieć i kompilator automatycznie wybierze poprawną | mogą ze sobą współistnieć i kompilator automatycznie wybierze poprawną | ||
Linia 32: | Linia 36: | ||
przypadkach zadziała mechanizm automatycznej dedukcji argumentu szablonu. | przypadkach zadziała mechanizm automatycznej dedukcji argumentu szablonu. | ||
<Source> | |||
int i,j,k; | |||
double x,t[20]; | |||
k=max(i,j); //wywołanie max(int,int) | |||
x=max(t,k); //wywołanie max<double>(double *,int) | |||
</Source> | |||
[ | ([[media:Max_overload.cpp | Źródło: max_overload.cpp]]) | ||
Możemy jednak chcieć nie tyle zdefiniować nową funkcję, ile zmienić | Możemy jednak chcieć nie tyle zdefiniować nową funkcję, ile zmienić | ||
kod już istniejącego szablonu tak aby dla pewnego podzbioru | kod już istniejącego szablonu, tak aby dla pewnego podzbioru | ||
parametrów działał inaczej. Np. działanie funkcji <tt>max</tt> | parametrów działał inaczej. Np. działanie funkcji <tt>max</tt> | ||
dla dwu wskaźników nie koniecznie jest tym czego byśmy sobie życzyli. | dla dwu wskaźników nie koniecznie jest tym, czego byśmy sobie życzyli. | ||
Możemy się spodziewać że w tej sytuacji funkcja powinna zwrócić | Możemy się spodziewać, że w tej sytuacji funkcja powinna zwrócić | ||
wskaźnik do większej wartości a nie wskaźnik o wyższym adresie. | wskaźnik do większej wartości, a nie wskaźnik o wyższym adresie. | ||
Definiujemy więc nowy przeciążony szablon funkcji <tt>max</tt>: | Definiujemy więc nowy przeciążony szablon funkcji <tt>max</tt>: | ||
<Source> | |||
template<typename T> T* max(T *a, T *b) { | |||
return ((*a)>(*b))?a:b; | |||
} | |||
[ | </Source> | ||
([[media:Max_overload.cpp | Źródło: max_overload.cpp]]) | |||
{{kotwica|przyklad_3.2|}}{{przyklad|3.2||}} | |||
Teraz sytuacja nie jest już jednoznaczna. Kompilator, napotykając wyrażenie | |||
może dopasować zarówno | <Source> | ||
lub szablon | int i,j; | ||
otrzymamy | max(&i,&j); | ||
</Source> | |||
może dopasować zarówno oryginalny szablon [[#przyklad_3.1|3.1]] z <tt>T <nowiki>=</nowiki> int*</tt> | |||
lub szablon [[#przyklad_3.2|3.2]] z <tt>T <nowiki>=</nowiki> int</tt>. I choć wydaje się że, | |||
otrzymamy błąd kompilacji, to do głosu dochodzi mechanizm rozstrzygania | |||
przeciążenia i kompilator wybierze dopasowanie drugiego szablonu jako | przeciążenia i kompilator wybierze dopasowanie drugiego szablonu jako | ||
“bardziej wyspecjalizowanego” tzn. do którego pasuje mniejszy zbiór | “bardziej wyspecjalizowanego”, tzn. do którego pasuje mniejszy zbiór | ||
argumentów. Ewidentnie algorytm rozstrzygania przeciążenia szablonów | argumentów. Ewidentnie algorytm rozstrzygania przeciążenia szablonów | ||
funkcji nie jest prosty, polega on na częściowym porządkowaniu | funkcji nie jest prosty, polega on na częściowym porządkowaniu | ||
przeciążonych funkcji według stopnia ich specjalizacji. Dokładny opis | przeciążonych funkcji według stopnia ich specjalizacji. Dokładny opis | ||
tego algorytmu można znaleźć w < | tego algorytmu można znaleźć w D. Vandervoorde, N. Josuttis <i>"C++ Szablony. Vademecum profesjonalisty"</i>, rozdz. 12. Z grubsza | ||
rzecz biorąc szablon funkcji <tt>F</tt> jest bardziej wyspecjalizowany niż | rzecz biorąc szablon funkcji <tt>F</tt> jest bardziej wyspecjalizowany niż | ||
szablon <tt>G</tt> jeśli każdy zestaw argumentów który da się dopasować do | szablon <tt>G</tt> jeśli każdy zestaw argumentów, który da się dopasować do | ||
<tt>F</tt> da sie również dopasować do szablonu <tt>G</tt> ale nie na odwrót. | <tt>F</tt> da sie również dopasować do szablonu <tt>G</tt>, ale nie na odwrót. | ||
W naszym przypadku do szablonu | W naszym przypadku do szablonu [[#przyklad_3.2|3.2]] da się dopasować | ||
argumenty typu <tt>(T *,T *)</tt>, które ewidentnie można dopasować | argumenty typu <tt>(T *,T *)</tt>, które ewidentnie można dopasować | ||
również do szablonu | również do szablonu [[#przyklad_3.1|3.1]]. Na odwrót już nie: | ||
<tt>(int,int)</tt>pasuje do | <tt>(int,int)</tt> pasuje do [[#przyklad_3.1|3.1]], a do szablonu [[#przyklad_3.2|3.2]] nie. | ||
==Specjalizacja szablonów funkcji== | ==Specjalizacja szablonów funkcji== | ||
Linia 81: | Linia 89: | ||
Przy dotychczasowych definicjach szablonów <tt>max</tt> | Przy dotychczasowych definicjach szablonów <tt>max</tt> | ||
<Source> | |||
template<typename T> T max(T a, T b); //(1) | |||
template<typename T> T* max(T *a, T *b); //(2) | |||
template<typename T> T max(T *data,size_t n); //(3) | |||
</Source> | |||
będziemy dalej mieli kłopoty z | będziemy dalej mieli kłopoty z | ||
funkcją <tt>max</tt> wywołaną dla argumentów typu <tt>char*</tt>. Takie | funkcją <tt>max</tt> wywołaną dla argumentów typu <tt>char*</tt>. Takie | ||
argumenty zwyczajowo oznaczają napisy. | argumenty zwyczajowo oznaczają napisy. Zgodnie z tym, co napisałem wcześniej, | ||
wywołany zostanie dla nich przeciążony szablon (2) i porówna | wywołany zostanie dla nich przeciążony szablon (2) i porówna | ||
tylko pierwsze litery napisów co ewidentnie nie jest tym czego się | tylko pierwsze litery napisów, co ewidentnie nie jest tym czego się | ||
oczekuje. | oczekuje. | ||
Na szczęście można dokonać specjalizacji tego szablonu dla | Na szczęście można dokonać specjalizacji tego szablonu dla | ||
argumentów typu <tt>char *<tt> i < | argumentów typu <tt>char *</tt> i <tt>const char *</tt>: | ||
<Source> | |||
template<> char *max<char *>(char *a,char *a) { | |||
return (strcmp(a,b)>0)?a:b; | |||
} | |||
template<> const char* max<const char *>(const char *a,const char *a) { | |||
return (strcmp(a,b)>0)?a:b; | |||
} | |||
</Source> | |||
Jak zwykle możemy pominąć argumenty szablonu podane w nawiasach ostrych | Jak zwykle możemy pominąć argumenty szablonu podane w nawiasach ostrych | ||
za nazwą szablonu jeśli mogą być one wydedukowane na podstawie | za nazwą szablonu, jeśli mogą być one wydedukowane na podstawie | ||
argumentów wywołania i | argumentów wywołania i najczęściej spotkamy się z następującym kodem: | ||
<Source> | |||
template<> char *max(char *a,char *a) { | |||
return (strcmp(a,b)>0)?a:b; | |||
} | |||
template<> const char* max(const char *a,const char *a) { | |||
return (strcmp(a,b)>0)?a:b; | |||
} | |||
</Source> | |||
[ | ([[media:Max_spec.cpp | Źródło: max_spec.cpp]]) | ||
Powyższe specjalizacje są pełne, tzn. określają | Powyższe specjalizacje są pełne, tzn. określają | ||
dokładnie wszystkie argumenty wywołania szablonu. Dlatego lista | dokładnie wszystkie argumenty wywołania szablonu. Dlatego lista | ||
parametrów szablonu w tych szablonach jest pusta. Tylko takie | parametrów szablonu w tych szablonach jest pusta. Tylko takie | ||
specjalizacje są dozwolone dla szablonów funkcji. Specjalizacja w | specjalizacje są dozwolone dla szablonów funkcji. Specjalizacja, w | ||
przeciwieństwie do przeciążenia musi dotyczyć już istniejącego | przeciwieństwie do przeciążenia, musi dotyczyć już istniejącego | ||
szablonu. Dlatego | szablonu. Dlatego niedozwolona jest specjalizacja: | ||
[ | <Source> | ||
template<> const char* max<char *>(char *a,const char *a) { | |||
return (strcmp(a,b)>0)?a:b;} | |||
</Source> | |||
([[media:Max_spec.cpp | Źródło: max_spec.cpp]]) | |||
{{przyklad|3.3||}} | {{kotwica|prz.3.3|}}{{przyklad|3.3||}} | ||
ponieważ argumenty są typu <tt>char *</tt> i <tt>const char *</tt>, i jako | ponieważ argumenty są typu <tt>char *</tt> i <tt>const char *</tt>, i jako | ||
Linia 136: | Linia 146: | ||
więc zdefiniować kolejne przeciążenie: | więc zdefiniować kolejne przeciążenie: | ||
<Source> | |||
template<typename T> const T* max(T *a,const T*b) { | |||
return (*a)>(*b))?a:b; | |||
} | |||
i dopiero wtedy kompilacja kodu | </Source> | ||
Sytuację podsumowuje | i dopiero wtedy kompilacja kodu [[#prz.3.3|3.3]] jest możliwa. | ||
Sytuację podsumowuje [[#rys.3.1|rysunek 3.1]]. | |||
< | {{kotwica|rys.3.1|}}<center> | ||
[[File:cpp-3-max_spec1.svg|600x350px|thumb|center|Rysunek 3.1.]] | |||
</center> | |||
Jawne podstawienie argumentów szablonu w miejsce parametru | Jawne podstawienie argumentów szablonu w miejsce parametru może | ||
prowadzić w przypadku istnienia szablonów przeciążonych do powstanie | prowadzić, w przypadku istnienia szablonów przeciążonych, do powstanie | ||
szeregu przeciążonych funkcji. Wtedy obowiązują "zwykłe" reguły | szeregu przeciążonych funkcji. Wtedy obowiązują "zwykłe" reguły | ||
rozstrzygania przeciążenia np. wyrażenie | rozstrzygania przeciążenia, np. wyrażenie | ||
<Source> | |||
max<int>(0,0); | |||
</Source> | |||
< | spowoduje "wygenerowanie" trzech funkcji: | ||
<Source> | |||
int max(int,int); | |||
int *max(int *,int*); | |||
int max(int *,int); | |||
</Source> | |||
([[media:Max_over_explicit.cpp | Źródło: max_over_explicit.cpp]]) | |||
Ponieważ zero lepiej pasuje do <tt>int</tt>-a niż do wskaźnika na | Ponieważ zero lepiej pasuje do <tt>int</tt>-a niż do wskaźnika na | ||
<tt>int</tt> wybrana | <tt>int</tt>, wybrana zostanie pierwsza z powyższych funkcji. | ||
==Funkcje zwykłe a szablony== | ==Funkcje zwykłe a szablony== | ||
Linia 168: | Linia 182: | ||
Obok szablonów mogą istnieć zwykłe funkcje o tej samej nazwie. | Obok szablonów mogą istnieć zwykłe funkcje o tej samej nazwie. | ||
Algorytm rozstrzygający przeciążenie preferuje dopasowanie zwykłych | Algorytm rozstrzygający przeciążenie preferuje dopasowanie zwykłych | ||
funkcji nad szablonami więc jeśli | funkcji nad szablonami, więc jeśli zdefiniujemy sobie funkcję | ||
<Source> | |||
int max(int i, int j); | |||
</Source> | |||
to kompilator dokona następujących podstawień: | to kompilator dokona następujących podstawień: | ||
<Source> | |||
max(0,1); //zwykla funkcja int max(int,int) | |||
max(0,1.0); //zwykla funkcja int max(int,int) z rzutowaniem double na int | |||
max(1.0,1.0); //szablon max<double>(double, double) | |||
</Source> | |||
([[media:Max_func.cpp | Źródło: max_func.cpp]]) | |||
Z pozoru specjalizacje pełne opisane w poprzedniej części zachowują | Z pozoru specjalizacje pełne opisane w poprzedniej części zachowują | ||
się jak zwykłe funkcje i moglibyśmy napisać: | się jak zwykłe funkcje i moglibyśmy napisać: | ||
<Source> | |||
char *max(char *a,char *a) { | |||
return (strcmp(a,b)>0)?a:b;} | |||
</Source> | |||
zamiast | zamiast | ||
<Source> | |||
template<> char *max<char *>(char *a,char *a) { | |||
return (strcmp(a,b)>0)?a:b;} | |||
</Source> | |||
Jest tak jednak tylko, jeśli możliwa jest dedukcja argumentów szablonu. | Jest tak jednak tylko, jeśli możliwa jest dedukcja argumentów szablonu. | ||
W przypadku szablonu | W przypadku szablonu | ||
<Source> | |||
template<typename T,typename U> T convert(U u) { | |||
return static_cast<T>(u); | |||
}; | |||
</Source> | |||
możemy zdefiniować np. specjalizacje: | |||
<Source> | |||
template<> int convert<int,double>(double u) {...}; | |||
template<> double convert<double,double>(double u) {...}; | |||
</Source> | |||
i używać ich podając jawnie pierwszy, niededukowalny argument szablonu: | i używać ich podając jawnie pierwszy, niededukowalny argument szablonu: | ||
<Source> | |||
convert<int>(3.14); | |||
convert<double>(2.71); | |||
</Source> | |||
natomiast | natomiast zdefiniowanie dwóch funkcji o tej samej nazwie i argumentach | ||
wywołania, różniących się tylko zwracanym typem nie jest możliwe. | wywołania, różniących się tylko zwracanym typem, nie jest możliwe. | ||
==Nieudane podstawienie nie jest błędem== | ==Nieudane podstawienie nie jest błędem== | ||
Jawne podstawienie wszystkich argumentów szablonu funkcji generuje nam | Jawne podstawienie wszystkich argumentów szablonu funkcji generuje nam | ||
jedną lub więcej funkcji "zwykłych". Może się jednak zdażyć że | jedną lub więcej funkcji "zwykłych". Może się jednak zdażyć, że | ||
niektóre podstawienia generują niepoprawny kod: | niektóre podstawienia generują niepoprawny kod: | ||
Linia 225: | Linia 243: | ||
t<int>(0); | t<int>(0); | ||
([[media:Sfinae.cpp | Źródło: sfinae.cpp]]) | |||
prowadzi do <tt>int::value</tt> i jest nieprawidłowe. Spowoduje to błąd | |||
kompilacji ale tylko wtedy jeśli nie | kompilacji, ale tylko wtedy, jeśli nie będzie innych przeciążonych | ||
szablonów funkcji <tt>t</tt>. Jeśli dodamy przeciążenie | szablonów funkcji <tt>t</tt>. Jeśli dodamy przeciążenie | ||
template<typename T> void t(T x ) {cerr<<"t2"<<endl;}; | template<typename T> void t(T x ) {cerr<<"t2"<<endl;}; | ||
([[media:Sfinae.cpp | Źródło: sfinae.cpp]]) | |||
to wyrażenie <tt>t<int>(0)</tt> zostanie do niego dopasowane. Innymi słowy | to wyrażenie <tt>t<int>(0)</tt> zostanie do niego dopasowane. Innymi słowy, | ||
algorytm | algorytm dopasowania przeciążenia pomija błędne podstawienia, nie | ||
generując błędów kompilacji. | generując błędów kompilacji. | ||
Linia 243: | Linia 261: | ||
Podobnie jak dla szablonów funkcji również dla szablonów klas istnieje | Podobnie jak dla szablonów funkcji również dla szablonów klas istnieje | ||
możliwość podania różnych implementacji dla różnych zestawów | możliwość podania różnych implementacji dla różnych zestawów | ||
argumentów szablonu. W przeciwieństwie jednak do szablonów funkcji | argumentów szablonu. W przeciwieństwie jednak do szablonów funkcji, | ||
szablony klas nie mogą być przeciążane a jedynie specjalizowane. | szablony klas nie mogą być przeciążane, a jedynie specjalizowane. | ||
Oznacza to w programie może istnieć tylko jeden szablon podstawowy o | Oznacza to, że w programie może istnieć tylko jeden szablon podstawowy o | ||
danej nazwie. Szablon podstawowy to szablon w którego definicji nie | danej nazwie. Szablon podstawowy to szablon, w którego definicji nie | ||
występują nawiasy ostre po nazwie szablonu. Wszystkie szablony | występują nawiasy ostre po nazwie szablonu. Wszystkie szablony | ||
prezentowane do tej pory były podstawowe. Z tej reguły wynika że trzy | prezentowane do tej pory były podstawowe. Z tej reguły wynika, że trzy | ||
zdefiniowane do tej pory szablony stosu | zdefiniowane do tej pory szablony stosu | ||
<Source> | |||
template<typename T> Stack {...}; | template<typename T> Stack {...}; | ||
template<typename T,int N | template<typename T,int N = 100> Stack {...}; //błąd szablon Stack już istnieje | ||
template<typename T,template<typename X> C> Stack { | template<typename T,template<typename X> C> Stack { | ||
C<T> _rep; | C<T> _rep; | ||
} / | } //błąd szablon Stack już istnieje | ||
</Source> | |||
nie mogą istnieć razem! | nie mogą istnieć razem! | ||
Linia 263: | Linia 282: | ||
trzeci szablon jest niedozwolony. | trzeci szablon jest niedozwolony. | ||
To ograniczenie można po części obejść dokonując specjalizacji | To ograniczenie można po części obejść, dokonując specjalizacji | ||
częściowej która jest dozwolona tylko dla szablonów klas i daje | częściowej, która jest dozwolona tylko dla szablonów klas i daje | ||
możliwość specjalizacji szablonu dla pewnego podzbioru jego argumentów | możliwość specjalizacji szablonu dla pewnego podzbioru jego argumentów, | ||
a nie dla pojedynczego zestawu jak specjalizacja pełna. Oczywiście | a nie dla pojedynczego zestawu, jak specjalizacja pełna. Oczywiście | ||
specjalizacja pełna też jest możliwa. Rozważmy następujący przykład | specjalizacja pełna też jest możliwa. Rozważmy następujący przykład, | ||
definiując szablon podstawowy: | definiując szablon podstawowy: | ||
template<typename T,int N | <Source> | ||
template<typename T,int N = 100> class Stack {}; | |||
</Source> | |||
możemy dokonać następujących specjalizacji: | możemy dokonać następujących specjalizacji: | ||
<Source> | |||
template<typename T> class Stack<T,666> {}; | template<typename T> class Stack<T,666> {}; | ||
template<typename T,int N> class Stack<T*,N> {}; | template<typename T,int N> class Stack<T*,N> {}; | ||
Linia 280: | Linia 301: | ||
template<> class Stack<double,666>{}; | template<> class Stack<double,666>{}; | ||
template<> class Stack<double *,44> {}; | template<> class Stack<double *,44> {}; | ||
/ | </Source> | ||
([[media:Stack_spec.cpp | Źródło: stack_spec.cpp]]) | |||
{{kotwica|rys.3.2|}}<center> | |||
[[File:cpp-3-zbiory.svg|500x350px|thumb|center|Rysunek 3.2. Symboliczne przedstawienie zbiorów argumentów dla różnych specjalizacji szablonu <tt>Stack<T,N></tt>.]] | |||
</center> | |||
Każda z tych specjalizacji definiuje pewien podzbiór parametrów | Każda z tych specjalizacji definiuje pewien podzbiór parametrów | ||
szablonu podstawowego (zob | szablonu podstawowego (zob. [[#rys.3.2|rysunek 3.2]]). Jeśli któryś z | ||
podzbiorów zawiera się w drugim to mówimy że jedna specjalizacja jest | podzbiorów zawiera się w drugim, to mówimy, że jedna specjalizacja jest | ||
bardziej wyspecjalizowana od drugiej. Hierarchia specjalizacji dla | bardziej wyspecjalizowana od drugiej. Hierarchia specjalizacji dla | ||
powyższego przykładu pokazana jest na | powyższego przykładu pokazana jest na [[#rys.3.3|rysunek 3.3]]. Jeżeli | ||
jakiś zestaw parametrów należy do | jakiś zestaw parametrów należy do dwóch (lub więcej) podzbiorów, które | ||
się przecinaja ale żeden nie zawiera się w drugim, to dla tych | się przecinaja, ale żeden nie zawiera się w drugim, to dla tych | ||
parametrów kompilator nie bedzie w stanie wybrać specjalizacji. | parametrów kompilator nie bedzie w stanie wybrać specjalizacji. | ||
{{kotwica|rys.3.3|}}<center> | |||
[[File:cpp-3-specstos.svg|750x350px|thumb|center|Rysunek 3.3. Uporządkownie specjalizacji szablonu <tt>Stack<T,N></tt>.]] | |||
</center> | |||
[ | |||
[ | |||
Oczywiście ten przykład jest bardzo sztuczny i trudno sobie wyobrazić | Oczywiście ten przykład jest bardzo sztuczny i trudno sobie wyobrazić | ||
powód tworzenia takich specjalizacji. Rozważmy bardziej realistyczny | powód tworzenia takich specjalizacji. Rozważmy bardziej realistyczny | ||
przypadek: | przypadek: deklarujemy szablon podstawowy, ale bez podawania jego | ||
definicji | definicji; będziemy korzystać jedynie z jego specjalizacji: | ||
template<typename T,int N | <Source> | ||
template<typename T,int N = 100, typename R = T*> class Stack; | |||
</Source> | |||
Następnie definiujemy dwie specjalizacje. Pierwszą dla stosów opartych | Następnie definiujemy dwie specjalizacje. Pierwszą dla stosów opartych | ||
o zwykłe tablice: | o zwykłe tablice: | ||
<Source> | |||
template<typename T,int N> class Stack<T,N,T*> { | template<typename T,int N> class Stack<T,N,T*> { | ||
T _rep[N]; | T _rep[N]; | ||
unsigned int _top; | unsigned int _top; | ||
public: | public: | ||
Stack():_top(0){}; | Stack():_top(0){}; | ||
void push(T e) {_rep[_top++] | void push(T e) {_rep[_top++]=e;} | ||
T pop() {return _rep[--_top];} | T pop() {return _rep[--_top];} | ||
}; | }; | ||
</Source> | |||
i drugą opartą o kontenery STL: | i drugą opartą o kontenery STL: | ||
<Source> | |||
template<typename T,int N,template<typename E> class Sequence> | template<typename T,int N,template<typename E> class Sequence> | ||
class Stack<T,N,Sequence<T> > { | class Stack<T,N,Sequence<T> > { | ||
Sequence<T> _rep; | Sequence<T> _rep; | ||
public: | public: | ||
void push(T e) {_rep.push_back(e);}; | void push(T e) {_rep.push_back(e);}; | ||
T pop() {T top | T pop() {T top = _rep.top();_rep.pop_back();return top;} | ||
bool is_empty() const {return _rep.empty();} | bool is_empty() const {return _rep.empty();} | ||
}; | }; | ||
/ | </Source> | ||
([[media:Stack_2.cpp | Źródło: Stack_2.cpp]]) | |||
Korzystając z tych specjalizacji możemy pisać następujący kod. | Korzystając z tych specjalizacji możemy pisać następujący kod. | ||
<Source> | |||
main() { | main() { | ||
Stack<int,100,int *> s_table; | Stack<int,100,int *> s_table; | ||
Stack<int,100> s_default ; | Stack<int,100> s_default ; | ||
Stack<int,0,std::vector<int> > s_container; | Stack<int,0,std::vector<int> > s_container; | ||
} | } | ||
/ | </Source> | ||
([[media:Stack_2.cpp | Źródło: Stack_2.cpp]]) | |||
W każdym przypadku kompilator wybierze implementację odpowiednią dla | W każdym przypadku kompilator wybierze implementację odpowiednią dla | ||
podanych parametrów. | podanych parametrów. | ||
==Szablony a dziedziczenie== | ==Szablony a dziedziczenie== | ||
Szablony klas mogą oczywiście dziedziczyć z innych klas. Deklaracja | Szablony klas mogą oczywiście dziedziczyć z innych klas. Deklaracja | ||
<Source> | |||
template<typename T> Stack: public Container { | template<typename T> Stack: public Container { | ||
... | ... | ||
}; | }; | ||
</Source> | |||
oznacza, że każda instancja danego szablonu <tt>Stack<T></tt> dziedziczy z | |||
klasy | klasy <tt>Container</tt>. Ponieważ konkretna instancja szablonu jest | ||
klasą to dowolna klasa czy szablon może dziedziczyć z instancji | klasą, to dowolna klasa czy szablon może dziedziczyć z instancji | ||
szablonu: | szablonu: | ||
<Source> | |||
class special Stack_int : public Stack<int> {...} | class special Stack_int : public Stack<int> {...} | ||
</Source> | |||
Definiując specjalizację szablonu klasy możemy dziedziczyć z innych | Definiując specjalizację szablonu klasy możemy dziedziczyć z innych | ||
specjalizacji tej samej klasy | specjalizacji tej samej klasy; nie może to jednak prowadzić do | ||
rekurencji. Jeśli napiszemy: | rekurencji. Jeśli napiszemy: | ||
<Source> | |||
template<typename T,int N> Stack {...}; | template<typename T,int N> Stack {...}; | ||
template<typename T> | template<typename T> | ||
Stack<T*,N>: private Stack<void *,N> {...}; | Stack<T*,N>: private Stack<void *,N> {...}; | ||
/ | </Source> | ||
([[media:Stack_void.cpp | Źródło: stack_void.cpp]]) | |||
to kompilator odmówi skompilowania tego kodu z powodu rekurencyjnej | to kompilator odmówi skompilowania tego kodu z powodu rekurencyjnej | ||
definicji specjalizacji szablonu | definicji specjalizacji szablonu <tt>Stack</tt>. Wszystko będzie w | ||
porządku jeśli dodamy specjalizację dla typu | porządku jeśli dodamy specjalizację dla typu <tt>void *</tt>: | ||
<Source> | |||
template<int N> class Stack<void *,N> {...} | template<int N> class Stack<void *,N> {...} | ||
/ | </Source> | ||
([[media:Stack_void.cpp | Źródło: stack_void.cpp]]) | |||
Dlaczego mielibyśmy jednak dziedziczyć implementację klasy | Dlaczego mielibyśmy jednak dziedziczyć implementację klasy <tt>void*</tt>? | ||
Powodem jest unikanie powielania kodu. Ponieważ każda konkretyzacja | Powodem jest unikanie powielania kodu. Ponieważ każda konkretyzacja | ||
(instancja) szablonu jest osobną klasa, to dla każdej generowany jest | (instancja) szablonu jest osobną klasa, to dla każdej generowany jest | ||
pełny kod potrzebnych funkcji. | pełny kod potrzebnych funkcji. Jeśli te funkcje są proste, to | ||
nie jest to kłopot. W praktyce implementacja stosu musi zwykle | nie jest to kłopot. W praktyce implementacja stosu musi zwykle | ||
uwzględniać dynamiczne zarządzanie pamięcią i może być dużo bardziej | uwzględniać dynamiczne zarządzanie pamięcią i może być dużo bardziej | ||
skomplikowana a zatem generowany kod będzie odpowiednio większy. | skomplikowana, a zatem generowany kod będzie odpowiednio większy. | ||
Ogólnie jest to nie do uniknięcia, ale ponieważ wszystkie wskaźniki | Ogólnie jest to nie do uniknięcia, ale ponieważ wszystkie wskaźniki | ||
mają ten sam rozmiar i można je rzutować na | mają ten sam rozmiar i można je rzutować na <tt>void *</tt> to możemy | ||
wykorzystać implementację | wykorzystać implementację <tt>Stack<void *></tt> do implementacji | ||
pozostałych typów wskaźnikowych: | pozostałych typów wskaźnikowych: | ||
<Source> | |||
template<typename T,size_t N> | template<typename T,size_t N> | ||
Stack<T*,N>: private Stack<void *,N> { | Stack<T*,N>: private Stack<void *,N> { | ||
public: | public: | ||
T* pop() { | T* pop() { | ||
return static_cast<T*>(Stack<void *>::pop()); | return static_cast<T*>(Stack<void *>::pop()); | ||
}; | }; | ||
void push(T *e) { | |||
void push(T *e) { | Stack<void *>::push(e); | ||
Stack<void *>::push(e); | } | ||
} | bool is_empty() {return Stack<void *>::is_empty();} | ||
bool is_empty() {return Stack<void *>::is_empty();} | |||
}; | }; | ||
/ | </Source> | ||
([[media:Stack_void.cpp | Źródło: stack_void.cpp]]) | |||
Korzystamy tu z automatycznej konwersji | Korzystamy tu z automatycznej konwersji <tt>T*</tt> na <tt>void *</tt>. W ten | ||
sposób np. kod funkcji | sposób, np. kod funkcji <tt>Stack<int *>::push(int *)</tt> | ||
będzie zawierał tylko parę instrukcji opakowujących wywołanie kodu | będzie zawierał tylko parę instrukcji opakowujących wywołanie kodu | ||
funkcji | funkcji <tt>Stack<void *>::push(void *)</tt>. Proszę zwrócić uwagę na | ||
zastosowanie dziedziczenia prywatnego. | zastosowanie dziedziczenia prywatnego. | ||
===Zależne klasy bazowe=== | ===Zależne klasy bazowe=== | ||
Szablon klasy może również dziedziczyć z innego szablonu klasy którego | Szablon klasy może również dziedziczyć z innego szablonu klasy, którego | ||
argumenty bedą zależały od jego parametrów: | argumenty bedą zależały od jego parametrów: | ||
<Source> | |||
template <typename T> class Base<T> {...}; | template <typename T> class Base<T> {...}; | ||
template<typename S> | template<typename S> | ||
class Derived: public Base<S> {}; | class Derived: public Base<S> {}; | ||
</Source> | |||
Przy tych definicjach klasa <tt>Derived<double></tt> dziedziczy z | |||
klasy | klasy <tt>Base<double></tt>. Taką klasę nazywamy zależną klasą bazową i | ||
jest to bardzo częsta konstrukcja w programowaniu uogólnionym. | jest to bardzo częsta konstrukcja w programowaniu uogólnionym. | ||
Linia 426: | Linia 457: | ||
wyszukiwaniem nazw, która może być sporym zaskoczeniem. Rozważmy | wyszukiwaniem nazw, która może być sporym zaskoczeniem. Rozważmy | ||
następujący przykład: | następujący przykład: | ||
<Source> | |||
template<typename T> class Base { | template<typename T> class Base { | ||
public: | public: | ||
Base():basefield(0){}; | Base():basefield(0){}; | ||
int basefield; | int basefield; | ||
}; | }; | ||
template<typename T> class DD :public Base<T> { | template<typename T> class DD :public Base<T> { | ||
public: | public: | ||
void f() {std::cerr<<basefield<<std::endl;} | void f() {std::cerr<<basefield<<std::endl;} | ||
}; | }; | ||
/ | </Source> | ||
([[media:Base.cpp | Źródło: base.cpp]]) | |||
Ten kod się nie skompiluje przy pomocy kompilatora C++ zgodnego ze | Ten kod się nie skompiluje przy pomocy kompilatora C++ zgodnego ze | ||
standardem. | standardem. Np. nie skompiluje go kompilator | ||
g++-3.4, a g++-3.3 tak. Powód tego faktu jest następujący: nazwa <tt>basefield</tt>, występująca w | |||
klasie <tt>DD</tt> jest nazwą niezależną (od parametru szablonu). Klasa | |||
bazowa, w której ta nazwa jest zdefiniowana jest klasą bazową zależną | |||
Powód tego faktu jest następujący: nazwa | |||
klasie | |||
bazowa w której ta nazwa jest zdefiniowana jest klasą bazową zależną | |||
(od parametru szablonu). Według standardu kompilator nie wyszukuje | (od parametru szablonu). Według standardu kompilator nie wyszukuje | ||
nazw niezależnych w zależnych klasach bazowych. Kompilator g++3.4 | nazw niezależnych w zależnych klasach bazowych. Kompilator g++-3.4 | ||
jest bliżej stadardu niż g++-3.3 i stąd to całe zamieszanie. | jest bliżej stadardu niż g++-3.3 i stąd to całe zamieszanie. Aby kod się skompilował należy uczynić tę nazwę zależną, np. poprzez kwalifikowanie jej nazwą klasy: | ||
<Source> | |||
uczynić | |||
template<typename T> class DD :public Base<T> { | template<typename T> class DD :public Base<T> { | ||
public: | public: | ||
void f() {std::cerr<<basefield<<std::endl;} | void f() {std::cerr<<DD::basefield<<std::endl;} | ||
}; | }; | ||
/ | </Source> | ||
([[media:Base.cpp | Źródło: base.cpp]]) | |||
lub przez | lub przez | ||
<Source> | |||
template<typename T> class DD :public Base<T> { | template<typename T> class DD :public Base<T> { | ||
public: | public: | ||
void f() {std::cerr<<this->basefield<<std::endl;} | void f() {std::cerr<<this->basefield<<std::endl;} | ||
}; | }; | ||
/ | </Source> | ||
([[media:Base.cpp | Źródło: base.cpp]]) | |||
===CRTP=== | ===CRTP=== | ||
Dziedziczenie szablonów można też wykorzystać do przydatnej | Dziedziczenie szablonów można też wykorzystać do przydatnej | ||
"sztuczki" zwanej po angielsku | "sztuczki", zwanej po angielsku <i>"couriously reccuring template | ||
pattern" | pattern"</i> (autorem tego idiomu jest James O. Coplien). Rozważmy następujący problem: chcemy | ||
zaimplementować mechanizm automatycznego liczenia ilości obiektów | zaimplementować mechanizm automatycznego liczenia ilości obiektów | ||
danej klasy. To standardowe zadanie na zastosowanie konstruktorów, | danej klasy. To standardowe zadanie na zastosowanie konstruktorów, | ||
destruktorów i statycznych składowych klasy: | destruktorów i statycznych składowych klasy: | ||
<Source> | |||
class Countable { | class Countable { | ||
protected: | protected: | ||
static size_t _counter; | static size_t _counter; | ||
public: | public: | ||
Countable() {++_counter;} | Countable() {++_counter;} | ||
Countable(const Countable &) {++_counter;} | Countable(const Countable &) {++_counter;} | ||
virtual | virtual ~Countable {--_counter} | ||
static size_t counter() {return _counter;} | |||
static size_t counter() {return | |||
}; | }; | ||
size_t Countable::_counter = 0; | |||
</Source> | |||
Oczywiście wpisywanie tego kodu do każdej klasy, której obiekty chcemy | |||
Oczywiście | |||
zliczać jest nużące i łamie zasadę niepowielania kodu. Postaramy się | zliczać jest nużące i łamie zasadę niepowielania kodu. Postaramy się | ||
więc wykorzystać kod klasy | więc wykorzystać kod klasy <tt>Countable</tt>, dziedzicząc go w innych klasach: | ||
<Source> | |||
class MyClass1 : public Countable { | class MyClass1 : public Countable { | ||
... | ... | ||
}; | }; | ||
class MyClass2 : public Countable { | class MyClass2 : public Countable { | ||
... | ... | ||
}; | }; | ||
</Source> | |||
Niestety ponieważ obie klasy | Niestety ponieważ obie klasy <tt>MyClass1</tt> i <tt>MyClass2</tt> dziedziczą | ||
z tej samej klasy, dziedziczą również ten sam wspólny licznik. Tak | z tej samej klasy, dziedziczą również ten sam wspólny licznik. Tak | ||
więc zliczaniu podlegać będą obiekty obu klas wspólnie. W rozwiązaniu | więc zliczaniu podlegać będą obiekty obu klas wspólnie. W rozwiązaniu | ||
pomogą nam szablony. Wystarczy uczynić klasę | pomogą nam szablony. Wystarczy uczynić klasę <tt>Countable</tt> szablonem | ||
template< | <Source> | ||
template<typename T> class Countable { | |||
protected: | protected: | ||
static size_t _counter; | static size_t _counter; | ||
public: | public: | ||
Countable() {++_counter;} | Countable() {++_counter;} | ||
Countable(const Countable &) {++_counter;}; | Countable(const Countable &) {++_counter;} | ||
virtual ~Countable() {--_counter} | |||
static size_t counter() {return _counter;} | |||
}; | |||
template<typename T> size_t Countable<T>::_counter = 0; | |||
</Source> | |||
([[media:Countable.cpp | Źródło: countable.cpp]]) | |||
i używać go w następujący sposób: | i używać go w następujący sposób: | ||
<Source> | |||
class MyClass1 : public Countable<MyClass1> { | class MyClass1 : public Countable<MyClass1> { | ||
... | ... | ||
}; | }; | ||
class MyClass2 : public Countable<MyClass2> { | class MyClass2 : public Countable<MyClass2> { | ||
... | ... | ||
}; | }; | ||
/ | </Source> | ||
([[media:Countable.cpp | Źródło: countable.cpp]]) | |||
Ponieważ każda konkretyzacja szablonu jest osobną klasą, klasy | Ponieważ każda konkretyzacja szablonu jest osobną klasą, klasy | ||
<tt>MyClass1</tt> i <tt>MyClass2</tt> dziedziczą z różnych klas bazowych i | |||
będą posiadać różne liczniki, ale ciągle wspólne w ramach każdej | będą posiadać różne liczniki, ale ciągle wspólne w ramach każdej | ||
klasy. Parametryzowanie klasy bazowej typem klasy dziedziczącej | klasy. Parametryzowanie klasy bazowej typem klasy dziedziczącej | ||
gwarantuje jej unikatowość. | gwarantuje jej unikatowość. | ||
Aktualna wersja na dzień 10:56, 3 paź 2021
Wprowadzenie
Mechanizm szablonów jest bardzo użyteczny ale może się okazać, że kod ogólny, który szablon implementuje, nie nadaje się do stosowania w każdym przypadku. W tej sytuacji mamy do dyspozycji dodatkowe własności implementacji szablonów w C++: przeciążanie i specjalizację. W poniższym wykładzie omówię sposób stosowania tych mechanizmów i różnice pomiędzy nimi.
Przeciążanie szablonów funkcji
Przeciążenie szablonu funkcji, podobnie jak przeciążenie zwykłych funkcji, definiuje nam nowy szablon. Możemy za pomocą przeciążenia zdefiniować np. funkcję służącą do znajdywania maksymalnego elementu w tablicy:
template<typename T> T max(T *data,size_t n) {
T _max = data[0];
for(size_t i=0;i<n;i++)
if(data[i]>_max) _max=data[i];
return _max;
}
Oba szablony: powyższy i wcześniej zdefiniowany
template<typename T> T max(T a,T b) {return (a>b)?a:b;};
Przykład 3.1
mogą ze sobą współistnieć i kompilator automatycznie wybierze poprawną definicję na podstawie argumentów wywołania funkcji. Oczywiście w obu przypadkach zadziała mechanizm automatycznej dedukcji argumentu szablonu.
int i,j,k;
double x,t[20];
k=max(i,j); //wywołanie max(int,int)
x=max(t,k); //wywołanie max<double>(double *,int)
Możemy jednak chcieć nie tyle zdefiniować nową funkcję, ile zmienić kod już istniejącego szablonu, tak aby dla pewnego podzbioru parametrów działał inaczej. Np. działanie funkcji max dla dwu wskaźników nie koniecznie jest tym, czego byśmy sobie życzyli. Możemy się spodziewać, że w tej sytuacji funkcja powinna zwrócić wskaźnik do większej wartości, a nie wskaźnik o wyższym adresie. Definiujemy więc nowy przeciążony szablon funkcji max:
template<typename T> T* max(T *a, T *b) {
return ((*a)>(*b))?a:b;
}
Przykład 3.2
Teraz sytuacja nie jest już jednoznaczna. Kompilator, napotykając wyrażenie
int i,j;
max(&i,&j);
może dopasować zarówno oryginalny szablon 3.1 z T = int* lub szablon 3.2 z T = int. I choć wydaje się że, otrzymamy błąd kompilacji, to do głosu dochodzi mechanizm rozstrzygania przeciążenia i kompilator wybierze dopasowanie drugiego szablonu jako “bardziej wyspecjalizowanego”, tzn. do którego pasuje mniejszy zbiór argumentów. Ewidentnie algorytm rozstrzygania przeciążenia szablonów funkcji nie jest prosty, polega on na częściowym porządkowaniu przeciążonych funkcji według stopnia ich specjalizacji. Dokładny opis tego algorytmu można znaleźć w D. Vandervoorde, N. Josuttis "C++ Szablony. Vademecum profesjonalisty", rozdz. 12. Z grubsza rzecz biorąc szablon funkcji F jest bardziej wyspecjalizowany niż szablon G jeśli każdy zestaw argumentów, który da się dopasować do F da sie również dopasować do szablonu G, ale nie na odwrót. W naszym przypadku do szablonu 3.2 da się dopasować argumenty typu (T *,T *), które ewidentnie można dopasować również do szablonu 3.1. Na odwrót już nie: (int,int) pasuje do 3.1, a do szablonu 3.2 nie.
Specjalizacja szablonów funkcji
Przy dotychczasowych definicjach szablonów max
template<typename T> T max(T a, T b); //(1)
template<typename T> T* max(T *a, T *b); //(2)
template<typename T> T max(T *data,size_t n); //(3)
będziemy dalej mieli kłopoty z funkcją max wywołaną dla argumentów typu char*. Takie argumenty zwyczajowo oznaczają napisy. Zgodnie z tym, co napisałem wcześniej, wywołany zostanie dla nich przeciążony szablon (2) i porówna tylko pierwsze litery napisów, co ewidentnie nie jest tym czego się oczekuje.
Na szczęście można dokonać specjalizacji tego szablonu dla argumentów typu char * i const char *:
template<> char *max<char *>(char *a,char *a) {
return (strcmp(a,b)>0)?a:b;
}
template<> const char* max<const char *>(const char *a,const char *a) {
return (strcmp(a,b)>0)?a:b;
}
Jak zwykle możemy pominąć argumenty szablonu podane w nawiasach ostrych za nazwą szablonu, jeśli mogą być one wydedukowane na podstawie argumentów wywołania i najczęściej spotkamy się z następującym kodem:
template<> char *max(char *a,char *a) {
return (strcmp(a,b)>0)?a:b;
}
template<> const char* max(const char *a,const char *a) {
return (strcmp(a,b)>0)?a:b;
}
Powyższe specjalizacje są pełne, tzn. określają dokładnie wszystkie argumenty wywołania szablonu. Dlatego lista parametrów szablonu w tych szablonach jest pusta. Tylko takie specjalizacje są dozwolone dla szablonów funkcji. Specjalizacja, w przeciwieństwie do przeciążenia, musi dotyczyć już istniejącego szablonu. Dlatego niedozwolona jest specjalizacja:
template<> const char* max<char *>(char *a,const char *a) {
return (strcmp(a,b)>0)?a:b;}
Przykład 3.3
ponieważ argumenty są typu char * i const char *, i jako takie nie pasują do żadnego z istniejących szablonów (1-3). Musimy więc zdefiniować kolejne przeciążenie:
template<typename T> const T* max(T *a,const T*b) {
return (*a)>(*b))?a:b;
}
i dopiero wtedy kompilacja kodu 3.3 jest możliwa. Sytuację podsumowuje rysunek 3.1.

Jawne podstawienie argumentów szablonu w miejsce parametru może prowadzić, w przypadku istnienia szablonów przeciążonych, do powstanie szeregu przeciążonych funkcji. Wtedy obowiązują "zwykłe" reguły rozstrzygania przeciążenia, np. wyrażenie
max<int>(0,0);
spowoduje "wygenerowanie" trzech funkcji:
int max(int,int);
int *max(int *,int*);
int max(int *,int);
( Źródło: max_over_explicit.cpp)
Ponieważ zero lepiej pasuje do int-a niż do wskaźnika na int, wybrana zostanie pierwsza z powyższych funkcji.
Funkcje zwykłe a szablony
Obok szablonów mogą istnieć zwykłe funkcje o tej samej nazwie. Algorytm rozstrzygający przeciążenie preferuje dopasowanie zwykłych funkcji nad szablonami, więc jeśli zdefiniujemy sobie funkcję
int max(int i, int j);
to kompilator dokona następujących podstawień:
max(0,1); //zwykla funkcja int max(int,int)
max(0,1.0); //zwykla funkcja int max(int,int) z rzutowaniem double na int
max(1.0,1.0); //szablon max<double>(double, double)
Z pozoru specjalizacje pełne opisane w poprzedniej części zachowują się jak zwykłe funkcje i moglibyśmy napisać:
char *max(char *a,char *a) {
return (strcmp(a,b)>0)?a:b;}
zamiast
template<> char *max<char *>(char *a,char *a) {
return (strcmp(a,b)>0)?a:b;}
Jest tak jednak tylko, jeśli możliwa jest dedukcja argumentów szablonu. W przypadku szablonu
template<typename T,typename U> T convert(U u) {
return static_cast<T>(u);
};
możemy zdefiniować np. specjalizacje:
template<> int convert<int,double>(double u) {...};
template<> double convert<double,double>(double u) {...};
i używać ich podając jawnie pierwszy, niededukowalny argument szablonu:
convert<int>(3.14);
convert<double>(2.71);
natomiast zdefiniowanie dwóch funkcji o tej samej nazwie i argumentach wywołania, różniących się tylko zwracanym typem, nie jest możliwe.
Nieudane podstawienie nie jest błędem
Jawne podstawienie wszystkich argumentów szablonu funkcji generuje nam jedną lub więcej funkcji "zwykłych". Może się jednak zdażyć, że niektóre podstawienia generują niepoprawny kod:
template<typename T> typename T::value t(T x) { cerr<<"t1"<<endl; };
Wywołanie
t<int>(0);
prowadzi do int::value i jest nieprawidłowe. Spowoduje to błąd kompilacji, ale tylko wtedy, jeśli nie będzie innych przeciążonych szablonów funkcji t. Jeśli dodamy przeciążenie
template<typename T> void t(T x ) {cerr<<"t2"<<endl;};
to wyrażenie t<int>(0) zostanie do niego dopasowane. Innymi słowy, algorytm dopasowania przeciążenia pomija błędne podstawienia, nie generując błędów kompilacji.
Specjalizacje szablonów klas
Podobnie jak dla szablonów funkcji również dla szablonów klas istnieje możliwość podania różnych implementacji dla różnych zestawów argumentów szablonu. W przeciwieństwie jednak do szablonów funkcji, szablony klas nie mogą być przeciążane, a jedynie specjalizowane. Oznacza to, że w programie może istnieć tylko jeden szablon podstawowy o danej nazwie. Szablon podstawowy to szablon, w którego definicji nie występują nawiasy ostre po nazwie szablonu. Wszystkie szablony prezentowane do tej pory były podstawowe. Z tej reguły wynika, że trzy zdefiniowane do tej pory szablony stosu
template<typename T> Stack {...};
template<typename T,int N = 100> Stack {...}; //błąd szablon Stack już istnieje
template<typename T,template<typename X> C> Stack {
C<T> _rep;
} //błąd szablon Stack już istnieje
nie mogą istnieć razem! Oczywiście w przypadku zastosowania domyślnych parametrów szablonu pierwsza definicja jest niepotrzebna, ale również bardziej pożyteczny trzeci szablon jest niedozwolony.
To ograniczenie można po części obejść, dokonując specjalizacji częściowej, która jest dozwolona tylko dla szablonów klas i daje możliwość specjalizacji szablonu dla pewnego podzbioru jego argumentów, a nie dla pojedynczego zestawu, jak specjalizacja pełna. Oczywiście specjalizacja pełna też jest możliwa. Rozważmy następujący przykład, definiując szablon podstawowy:
template<typename T,int N = 100> class Stack {};
możemy dokonać następujących specjalizacji:
template<typename T> class Stack<T,666> {};
template<typename T,int N> class Stack<T*,N> {};
template<int N> class Stack<double ,N> {};
template<int N> class Stack<int *,N> {};
template<> class Stack<double,666>{};
template<> class Stack<double *,44> {};

Każda z tych specjalizacji definiuje pewien podzbiór parametrów szablonu podstawowego (zob. rysunek 3.2). Jeśli któryś z podzbiorów zawiera się w drugim, to mówimy, że jedna specjalizacja jest bardziej wyspecjalizowana od drugiej. Hierarchia specjalizacji dla powyższego przykładu pokazana jest na rysunek 3.3. Jeżeli jakiś zestaw parametrów należy do dwóch (lub więcej) podzbiorów, które się przecinaja, ale żeden nie zawiera się w drugim, to dla tych parametrów kompilator nie bedzie w stanie wybrać specjalizacji.

Oczywiście ten przykład jest bardzo sztuczny i trudno sobie wyobrazić powód tworzenia takich specjalizacji. Rozważmy bardziej realistyczny przypadek: deklarujemy szablon podstawowy, ale bez podawania jego definicji; będziemy korzystać jedynie z jego specjalizacji:
template<typename T,int N = 100, typename R = T*> class Stack;
Następnie definiujemy dwie specjalizacje. Pierwszą dla stosów opartych o zwykłe tablice:
template<typename T,int N> class Stack<T,N,T*> {
T _rep[N];
unsigned int _top;
public:
Stack():_top(0){};
void push(T e) {_rep[_top++]=e;}
T pop() {return _rep[--_top];}
};
i drugą opartą o kontenery STL:
template<typename T,int N,template<typename E> class Sequence>
class Stack<T,N,Sequence<T> > {
Sequence<T> _rep;
public:
void push(T e) {_rep.push_back(e);};
T pop() {T top = _rep.top();_rep.pop_back();return top;}
bool is_empty() const {return _rep.empty();}
};
Korzystając z tych specjalizacji możemy pisać następujący kod.
main() {
Stack<int,100,int *> s_table;
Stack<int,100> s_default ;
Stack<int,0,std::vector<int> > s_container;
}
W każdym przypadku kompilator wybierze implementację odpowiednią dla podanych parametrów.
Szablony a dziedziczenie
Szablony klas mogą oczywiście dziedziczyć z innych klas. Deklaracja
template<typename T> Stack: public Container {
...
};
oznacza, że każda instancja danego szablonu Stack<T> dziedziczy z klasy Container. Ponieważ konkretna instancja szablonu jest klasą, to dowolna klasa czy szablon może dziedziczyć z instancji szablonu:
class special Stack_int : public Stack<int> {...}
Definiując specjalizację szablonu klasy możemy dziedziczyć z innych specjalizacji tej samej klasy; nie może to jednak prowadzić do rekurencji. Jeśli napiszemy:
template<typename T,int N> Stack {...};
template<typename T>
Stack<T*,N>: private Stack<void *,N> {...};
to kompilator odmówi skompilowania tego kodu z powodu rekurencyjnej definicji specjalizacji szablonu Stack. Wszystko będzie w porządku jeśli dodamy specjalizację dla typu void *:
template<int N> class Stack<void *,N> {...}
Dlaczego mielibyśmy jednak dziedziczyć implementację klasy void*? Powodem jest unikanie powielania kodu. Ponieważ każda konkretyzacja (instancja) szablonu jest osobną klasa, to dla każdej generowany jest pełny kod potrzebnych funkcji. Jeśli te funkcje są proste, to nie jest to kłopot. W praktyce implementacja stosu musi zwykle uwzględniać dynamiczne zarządzanie pamięcią i może być dużo bardziej skomplikowana, a zatem generowany kod będzie odpowiednio większy. Ogólnie jest to nie do uniknięcia, ale ponieważ wszystkie wskaźniki mają ten sam rozmiar i można je rzutować na void * to możemy wykorzystać implementację Stack<void *> do implementacji pozostałych typów wskaźnikowych:
template<typename T,size_t N>
Stack<T*,N>: private Stack<void *,N> {
public:
T* pop() {
return static_cast<T*>(Stack<void *>::pop());
};
void push(T *e) {
Stack<void *>::push(e);
}
bool is_empty() {return Stack<void *>::is_empty();}
};
Korzystamy tu z automatycznej konwersji T* na void *. W ten sposób, np. kod funkcji Stack<int *>::push(int *) będzie zawierał tylko parę instrukcji opakowujących wywołanie kodu funkcji Stack<void *>::push(void *). Proszę zwrócić uwagę na zastosowanie dziedziczenia prywatnego.
Zależne klasy bazowe
Szablon klasy może również dziedziczyć z innego szablonu klasy, którego argumenty bedą zależały od jego parametrów:
template <typename T> class Base<T> {...};
template<typename S>
class Derived: public Base<S> {};
Przy tych definicjach klasa Derived<double> dziedziczy z klasy Base<double>. Taką klasę nazywamy zależną klasą bazową i jest to bardzo częsta konstrukcja w programowaniu uogólnionym.
Z zależnymi klasami bazowymi wiąże się jednak pewna zasada, związana z wyszukiwaniem nazw, która może być sporym zaskoczeniem. Rozważmy następujący przykład:
template<typename T> class Base {
public:
Base():basefield(0){};
int basefield;
};
template<typename T> class DD :public Base<T> {
public:
void f() {std::cerr<<basefield<<std::endl;}
};
Ten kod się nie skompiluje przy pomocy kompilatora C++ zgodnego ze standardem. Np. nie skompiluje go kompilator g++-3.4, a g++-3.3 tak. Powód tego faktu jest następujący: nazwa basefield, występująca w klasie DD jest nazwą niezależną (od parametru szablonu). Klasa bazowa, w której ta nazwa jest zdefiniowana jest klasą bazową zależną (od parametru szablonu). Według standardu kompilator nie wyszukuje nazw niezależnych w zależnych klasach bazowych. Kompilator g++-3.4 jest bliżej stadardu niż g++-3.3 i stąd to całe zamieszanie. Aby kod się skompilował należy uczynić tę nazwę zależną, np. poprzez kwalifikowanie jej nazwą klasy:
template<typename T> class DD :public Base<T> {
public:
void f() {std::cerr<<DD::basefield<<std::endl;}
};
lub przez
template<typename T> class DD :public Base<T> {
public:
void f() {std::cerr<<this->basefield<<std::endl;}
};
CRTP
Dziedziczenie szablonów można też wykorzystać do przydatnej "sztuczki", zwanej po angielsku "couriously reccuring template pattern" (autorem tego idiomu jest James O. Coplien). Rozważmy następujący problem: chcemy zaimplementować mechanizm automatycznego liczenia ilości obiektów danej klasy. To standardowe zadanie na zastosowanie konstruktorów, destruktorów i statycznych składowych klasy:
class Countable {
protected:
static size_t _counter;
public:
Countable() {++_counter;}
Countable(const Countable &) {++_counter;}
virtual ~Countable {--_counter}
static size_t counter() {return _counter;}
};
size_t Countable::_counter = 0;
Oczywiście wpisywanie tego kodu do każdej klasy, której obiekty chcemy zliczać jest nużące i łamie zasadę niepowielania kodu. Postaramy się więc wykorzystać kod klasy Countable, dziedzicząc go w innych klasach:
class MyClass1 : public Countable {
...
};
class MyClass2 : public Countable {
...
};
Niestety ponieważ obie klasy MyClass1 i MyClass2 dziedziczą z tej samej klasy, dziedziczą również ten sam wspólny licznik. Tak więc zliczaniu podlegać będą obiekty obu klas wspólnie. W rozwiązaniu pomogą nam szablony. Wystarczy uczynić klasę Countable szablonem
template<typename T> class Countable {
protected:
static size_t _counter;
public:
Countable() {++_counter;}
Countable(const Countable &) {++_counter;}
virtual ~Countable() {--_counter}
static size_t counter() {return _counter;}
};
template<typename T> size_t Countable<T>::_counter = 0;
i używać go w następujący sposób:
class MyClass1 : public Countable<MyClass1> {
...
};
class MyClass2 : public Countable<MyClass2> {
...
};
Ponieważ każda konkretyzacja szablonu jest osobną klasą, klasy MyClass1 i MyClass2 dziedziczą z różnych klas bazowych i będą posiadać różne liczniki, ale ciągle wspólne w ramach każdej klasy. Parametryzowanie klasy bazowej typem klasy dziedziczącej gwarantuje jej unikatowość.