Zaawansowane CPP/Wykład 3: Szablony II: Różnice pomiędzy wersjami

Z Studia Informatyczne
Przejdź do nawigacjiPrzejdź do wyszukiwania
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 79 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:


template<typename T> T max(T *data,size_t n) {
<Source>
T _max <nowiki>=</nowiki> data[0];
template<typename T> T max(T *data,size_t n) {
for(size_t i<nowiki>=</nowiki>0;i<n;i++i)
  T _max = data[0];
        if(data[i]>_max) _max<nowiki>=</nowiki>data[i];
  for(size_t i=0;i<n;i++)
return _max;
    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


template<typename T> T max(T a,T b) {return (a>b)?a:b;};
<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.


int i,j,k;
<Source>
double x,t[20];<br>
int i,j,k;
k<nowiki>=</nowiki>max(i,j);   //<-- wywołanie <tt>max<int>(int,int)</tt> -->//
double x,t[20];
x<nowiki>=</nowiki>max(t,k);   //<-- wywołanie <tt>max<double>(double *,int)</tt>-->//
k=max(i,j); //wywołanie max(int,int)
x=max(t,k); //wywołanie max<double>(double *,int)
</Source>


[http://osilek.mimuw.edu.pl/images/2/27/Max_overload.cpp Źródło: Max_overload]
([[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>:


template<typename T> T* max(T *a,max T *b) {
<Source>
return ((*a)>(*b))?a:b;
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]])


[http://osilek.mimuw.edu.pl/images/2/27/Max_overload.cpp Źródło: Max_overload]
{{kotwica|przyklad_3.2|}}{{przyklad|3.2||}}


{{przyklad|3.2||}}
Teraz sytuacja nie jest już jednoznaczna. Kompilator, napotykając wyrażenie


Teraz sytuacja nie jest już jednoznaczna. Kompilator napotykając wyrażenie
<Source>
 
int i,j;
int i,j;
max(&i,&j);
max(&i,&j);
</Source>
 
może dopasować zarówno oryginalny szablon [[#przyklad_3.1|3.1]] z <tt>T <nowiki>=</nowiki> int*</tt>
może dopasować zarówno orginalny szablon <font color=red>{[##ex:max1|Uzupelnic ex:max1|]}</font> 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,
lub szablon <font color=red>{[##ex:max2|Uzupelnic ex:max2|]}</font> z <tt>T <nowiki>=</nowiki> int</tt>. I chodź wydaje się że
otrzymamy błąd kompilacji, to do głosu dochodzi mechanizm rozstrzygania
otrzymamy bład kompilacji, to do głosu dochodzi mechanizm rostrzygania
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 <tt>szablony</tt>&nbsp;rozd.&nbsp;12. Z grubsza
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 <font color=red>{[##ex:max2|Uzupelnic ex:max2|]}</font> da się dopasować
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 <font color=red>{[##ex:max1|Uzupelnic ex:max1|]}</font>. Na odwrót już nie: np
również do szablonu [[#przyklad_3.1|3.1]]. Na odwrót już nie:
<tt>(int,int)</tt>pasuje do <font color=red>{[##ex:max1|Uzupelnic ex:max1|]}</font> a do szablonu <font color=red>{[##ex:max2|Uzupelnic ex:max2|]}</font> nie.
<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>


template<typename T> T  max(T a,max T b);     //<-- (1) -->//
<Source>
template<typename T> T* max(T *a,max T *b);   //<-- (2) -->//
template<typename T> T  max(T a, T b);         //(1)
template<typename T> T  max(T *data,size_t n); //<-- (3) -->//
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. Zgodne z tym co napisałem wcześniej,  
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 </tt>const char *</tt>:
argumentów typu <tt>char *</tt> i <tt>const char *</tt>:
 
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>
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 częściej spotkamy się z następującym kodem:   
argumentów wywołania i najczęściej spotkamy się z następującym kodem:   


template<> char *max(char *a,char *a) {
<Source>
        return (strcmp(a,b)>0)?a:b;
template<> char *max(char *a,char *a) {
      }
  return (strcmp(a,b)>0)?a:b;
template<> const char *
}
max(const char *a,const char *a) {
template<> const char* max(const char *a,const char *a) {
        return (strcmp(a,b)>0)?a:b;
  return (strcmp(a,b)>0)?a:b;
      }   
}   
 
</Source>
[http://osilek.mimuw.edu.pl/images/e/e2/Max_spec.cpp Źródło: Max_spec.cpp]
([[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 nie dozwolona jest specjalizacja:
szablonu. Dlatego niedozwolona jest specjalizacja:
 
template<> const char *
max<char *>(char *a,const char *a) {
  return (strcmp(a,b)>0)?a:b;}
 
[http://osilek.mimuw.edu.pl/images/e/e2/Max_spec.cpp Źródło: Max_spec.cpp]


<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]])


{{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:


template<typename T> const T* max(T *a,const T*b) {
<Source>
return (*a)>(*b))?a:b;
template<typename T> const T* max(T *a,const T*b) {
}
  return (*a)>(*b))?a:b;
 
}
i dopiero wtedy kompilacja kodu <font color=red>{[##ex:max-spec1|Uzupelnic ex:max-spec1|]}</font> się powiedzie możliwa.   
</Source>
Sytuację podsumowuje rysunek&nbsp;<font color=red>{[##fig:max-spec|Uzupelnic fig:max-spec|]}</font>.  
i dopiero wtedy kompilacja kodu [[#prz.3.3|3.3]] jest możliwa.   
Sytuację podsumowuje [[#rys.3.1|rysunek 3.1]].  


<font color=red>[p][height<nowiki>=</nowiki>,angle<nowiki>=</nowiki>90]{mod03/graphics/max_spec1.eps}<br>
{{kotwica|rys.3.1|}}<center>
{}</font>
[[File:cpp-3-max_spec1.svg|600x350px|thumb|center|Rysunek 3.1.]]
</center>


Jawne podstawienie argumentów szablonu w miejsce parametru, może
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  
 
max<int>(0,0);
 
spowoduje "wygenerowanie" trzech funkcji


int  max(int,int);
<Source>
int  *max(int *,int*);
max<int>(0,0);
int  max(int *,int);
</Source>


Źródło: Maxoverexplicit.cpp
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 zostaniene pierwsza z powyższych funkcji.
<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, zdefiniujemy sobie funkcję
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ń:


int max(int i, int j)
<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>


to kompilator dokona następujących podstawień:
([[media:Max_func.cpp | Źródło: max_func.cpp]])
 
max(0,1);    /*zwykla funkcja <tt>int max(int,int)</tt>*/
max(0,1.0);  /* {12cm}{zwykla funkcja <tt>int max(int,int)</tt>
z rzutowaniem <tt>double</tt> na <tt>int</tt>}*/
max(1.0,1.0); /* szablon max<double>(double, double)*/
/*{mod03/code/max_func.cpp}{maxfunc.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) {
char *max(char *a,char *a) {
return (strcmp(a,b)>0)?a:b;}
  return (strcmp(a,b)>0)?a:b;}
 
</Source>
zamiast
zamiast
 
<Source>
template<> char *max<char *>(char *a,char *a) {
template<> char *max<char *>(char *a,char *a) {
return (strcmp(a,b)>0)?a:b;}
  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) {
template<typename T,typename U> T convert(U u) {
return static_cast<T>(u);
  return static_cast<T>(u);
};
};
</Source>
możemy zdefiniować np. specjalizacje:


możememy zdefiniować np. specjalizacje:
<Source>
 
template<> int    convert<int,double>(double u) {...};
template<> int    convert<int,double>(U u) {...};
template<> double convert<double,double>(double u) {...};
template<> double convert<double,double>(U 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<int>(3.14);
convert<double>(2.71);
convert<double>(2.71);
 
</Source>
natomiast zdefiniowane dwu funkcji o tej samej nazwie i argumentach
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:


template<typename T> typename T::value t(T x) {
template<typename T> typename T::value t(T x) {
cerr<<"t1"<<endl;
cerr<<"t1"<<endl;
};
};


Wywołanie
Wywołanie


t<int>(0);
  t<int>(0);
/*{mod03/code/sfinae.cpp}{sfinae.cpp}
 
([[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 będzie innych przeciążonych
szablonów funkcji <tt>t</tt>. Jeśli dodamy przeciążenie


prowadz do {int::value} i jest nieprawidłowe. Spowoduje to błąd
template<typename T> void t(T x ) {cerr<<"t2"<<endl;};
kompilacji ale tylko wtedy jeśli nie bedzie innych przeciążonych
szablonów funkcji {t}. Jeśli dodamy przeciążenie


template<typename T> void t(T x ) {cerr<<"t2"<<endl;};
([[media:Sfinae.cpp | Źródło: sfinae.cpp]])
/*{mod03/code/sfinae.cpp}{sfinae.cpp}


to wyrażenie {t<int>(0)} zostanie do niego dopasowane. Innymi słowy
to wyrażenie <tt>t<int>(0)</tt> zostanie do niego dopasowane. Innymi słowy,
algorytm dopasowanie przeciążenia pomija błędne podstawienia nie
algorytm dopasowania przeciążenia pomija błędne podstawienia, nie
generując błędów kompilacji.  
generując błędów kompilacji.


==Specjalizacje szablonów klas==
==Specjalizacje szablonów klas==
Linia 241: 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 <nowiki>=</nowiki> 100> stos {...}; /* błąd szablon
template<typename T,int N = 100> Stack {...}; //błąd szablon Stack już istnieje
<tt>Stack</tt> juz 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 <tt>Stack</tt> juz istnieje */
} //błąd szablon Stack już istnieje
</Source>


nie mogą istnieć razem!
nie mogą istnieć razem!
Linia 261: 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 <nowiki>=</nowiki> 100> class Stack {};
<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 278: Linia 301:
template<>                  class Stack<double,666>{};
template<>                  class Stack<double,666>{};
template<>                  class Stack<double *,44> {};
template<>                  class Stack<double *,44> {};
/*{mod03/code/stack_spec.cpp}{stackspec.cpp}*/
</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 rysynek&nbsp;[[##fig:zbiory|Uzupelnic fig:zbiory|]]). Jeśli któryś z
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 rys&nbsp;[[##fig:specstos|Uzupelnic fig:specstos|]].  Jeżeli
powyższego przykładu pokazana jest na [[#rys.3.3|rysunek 3.3]].  Jeżeli
jakiś zestaw parametrów należy do dwu (lub więcej podzbiorów) które
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.
[p]


[height<nowiki>=</nowiki>,angle<nowiki>=</nowiki>90]{mod03/graphics/zbiory.eps}
{{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>.]]
{Symboliczne przedstawienie zbiorów argumentów dla różnych specjalizacji szablonu {Stack<T,N>}}
</center>
 
[p]
 
[height<nowiki>=</nowiki>,angle<nowiki>=</nowiki>90]{mod03/graphics/specstos.eps}
 
{Uporządkownie specjalizacji szablonu
{Stack<T,N>}}


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: Deklarujemy szablon podstawowy, ale bez podawania jego
przypadek: deklarujemy szablon podstawowy, ale bez podawania jego
definicji. Będziemy korzystac jedynie z jego spejalizacji:
definicji; będziemy korzystać jedynie z jego specjalizacji:


template<typename T,int N <nowiki>=</nowiki> 100, typename R <nowiki>=</nowiki> T*> class Stack;
<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++]<nowiki>=</nowiki>e;}
  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 <nowiki>=</nowiki> _rep.top();_rep.pop_back();return 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();}
};
};
/*{mod03/code/stack.cpp}{stack.cpp}*/
</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;
}
}
/*{mod03/code/stack.cpp}{stack.cpp}*/
</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 to że każda instancja danego szablonu {Stack<T>} dziedziczy z
oznacza, że każda instancja danego szablonu <tt>Stack<T></tt> dziedziczy z
klasy {Container}. Ponieważ konkretna instancja szablonu jest
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, nie może to jednak prowadzić do
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> {...};
/*{mod03/code/stack_void.cpp}{stackvoid.cpp}*/
</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 {Stack}. Wszystko będzie w
definicji specjalizacji szablonu <tt>Stack</tt>. Wszystko będzie w
porządku  jeśli dodamy specjalizację  dla typu {void *}:
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> {...}
/*{mod03/code/stack_void.cpp}{stackvoid.cpp}*/
</Source>
([[media:Stack_void.cpp | Źródło: stack_void.cpp]])


Dlaczego mielibyśmy jednak dziedziczyć implementację klasy {void*}?
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. Jesli te funkcje są proste to
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 {void *} to możemy
mają ten sam rozmiar i można je rzutować na <tt>void *</tt> to możemy
wykorzystać implementację {Stack<void *>} do implementacji
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();}  
};
};
/*{mod03/code/stack_void.cpp}{stackvoid.cpp}*/
</Source>
([[media:Stack_void.cpp | Źródło: stack_void.cpp]])


Korzystamy tu z automatycznej konwersji {T*} na {void *}.  W ten
Korzystamy tu z automatycznej konwersji <tt>T*</tt> na <tt>void *</tt>.  W ten
sposób np. kod funkcji {Stack<int *>::push(int *)}
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 {Stack<void *>::push(void *)}. Proszę zwrócić uwagę na
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 {Derived<double>} dziedziczy z
Przy tych definicjach klasa <tt>Derived<double></tt> dziedziczy z
klasy {Base<double>}. Taką klasę nazywamy zależną klasą bazową i
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 424: 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;}  
};
};
/*{mod03/code/base.cpp}{base.cpp}*/
</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. Ten fakt mało nie przyprawił mnie o zawał serca, kiedy kod
standardem. Np. nie skompiluje go kompilator
nad którym pracowałem ponad miesiąc przestał się kompilować. Dopiero
g++-3.4, a g++-3.3 tak. Powód tego faktu jest następujący: nazwa <tt>basefield</tt>, występująca w
po dobrej chwili zorientowałem się że powodem jest zmiana kompilatora
klasie <tt>DD</tt> jest nazwą niezależną (od parametru szablonu). Klasa
z g++-3.3 na g++3.4 a nie wirus który uszkodził kod źródłówy.
bazowa, w której ta nazwa jest zdefiniowana jest klasą bazową zależną
 
Powód tego faktu jest następujący: nazwa {basefield} występująca w
klasie {DD} jest nazwą niezależna (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
(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ć nazwę zależną, np. poprzez kwalifikowanie jej nazwą klasy:
Więcej o tym napiszę w
<Source>
module <nazwy>, a na razie podam tylko rozwiązanie problemu. Należy
uczynić nazwę zależną np.poprzez kwalifikowanie jej nazwą klasy:
 
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;}  
};
};
/*{mod03/code/base.cpp}{base.cpp}*/
</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;}  
};
};
/*{mod03/code/base.cpp}{base.cpp}*/
</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 ''"couriously reccuring template
"sztuczki", zwanej po angielsku <i>"couriously reccuring template
pattern"''{coplien}. Rozważmy następujący problem: chcemy
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 &nbsp;Countable {--_counter};
  virtual ~Countable {--_counter}
 
  static size_t counter()  {return _counter;}  
static size_t counter()  {return _conter;}  
};
};
size_t Countable::_counter = 0;
</Source>


size_t Contable::_counter <nowiki>=</nowiki> 0;
Oczywiście wpisywanie tego kodu do każdej klasy, której obiekty chcemy
 
Oczywiście wspisywanie tego kodu do każdej klasy której obiekty chcemy
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 {Countable} dziedzicząc go w innych klasach:
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 {MyClass1} i {MyClass2} dziedziczą
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ę {Countable} szablonem
pomogą nam szablony. Wystarczy uczynić  klasę <tt>Countable</tt> szablonem


template<tyname T> class Countable {
<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>


virtual &nbsp;Countable() {--_counter};
([[media:Countable.cpp | Źródło: countable.cpp]])
static size_t counter()  {return _counter;}
};
template<typename T> size_t Contable<T>::_counter <nowiki>=</nowiki> 0;
/*{mod03/code/countable.cpp}{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> {
...
  ...
};
};
/*{mod03/code/countable.cpp}{countable.cpp}*/
</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
{MyClass1} i {MyClass2} dziedziczą z różnych klas bazowych i
<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ść.
 
==Podsumowanie==
 
==Przypisy==
</references>

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)

( Źródło: max_overload.cpp)

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;
}

( Źródło: max_overload.cpp)

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;
}

( Źródło: max_spec.cpp)

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;}

( Źródło: max_spec.cpp)

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.

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)

( Źródło: max_func.cpp)

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);

( Źródło: sfinae.cpp)

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;};

( Źródło: sfinae.cpp)

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> {};

( Źródło: stack_spec.cpp)


Rysunek 3.2. Symboliczne przedstawienie zbiorów argumentów dla różnych specjalizacji szablonu Stack<T,N>.

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.

Rysunek 3.3. Uporządkownie specjalizacji szablonu Stack<T,N>.

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();}
};

( Źródło: Stack_2.cpp)

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;
}

( Źródło: Stack_2.cpp)

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> {...};

( Źródło: stack_void.cpp)

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> {...}

( Źródło: stack_void.cpp)

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();} 
};

( Źródło: stack_void.cpp)

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;} 
};

( Źródło: base.cpp)

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;} 
};

( Źródło: base.cpp)

lub przez

template<typename T> class DD :public Base<T> {       
public:
  void f() {std::cerr<<this->basefield<<std::endl;} 
};

( Źródło: base.cpp)

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;

( Źródło: countable.cpp)

i używać go w następujący sposób:

class MyClass1 : public Countable<MyClass1> {
  ...
};
class MyClass2 : public Countable<MyClass2> {
  ...
};

( Źródło: countable.cpp)

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ść.