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

Z Studia Informatyczne
Przejdź do nawigacjiPrzejdź do wyszukiwania
Matiunreal (dyskusja | edycje)
 
(Nie pokazano 179 wersji utworzonych przez 8 użytkowników)
Linia 1: Linia 1:
==Wprowadzenie==
 


==Szablony funkcji==
==Szablony funkcji==
Linia 6: Linia 6:


W praktyce programowania często spotykamy się z funkcjami
W praktyce programowania często spotykamy się z funkcjami
(algorytmami) które można zastosować do szerokiej klasy typów i
(algorytmami), które można zastosować do szerokiej klasy typów i
struktur danych. Typowym przykładem jest funkcja obliczająca maksimum
struktur danych. Typowym przykładem jest funkcja obliczająca maksimum
dwu wartości. Ten trywialny aczkolwiek przydatny kod można zapisać np.
dwu wartości. Ten trywialny, aczkolwiek przydatny kod można zapisać np.
w postaci:
w postaci:


    int max(int a,int b) {
int max(int a,int b) {
      return (a>b)?a:b;
  return (a>b)?a:b;
    };  
};  


{{przyklad|[1.1]||}}
{{kotwica|prz.1.1|}}{{przyklad|1.1||}}


Funkcja <tt>max</tt> wybiera większy z dwu <tt>int</tt>-ów ale widać że kod
Funkcja <tt>max</tt> wybiera większy z dwu <tt>int</tt>-ów, ale widać, że kod
będzie identyczny dla argumentów dowolnego innego typu pod warunkiem
będzie identyczny dla argumentów dowolnego innego typu pod warunkiem,
że istnieje dla niego operator porównania i konstruktor kopiujący. W
istnieje dla niego operator porównania i konstruktor kopiujący. W
językach programowania z silną kontrolą typów, takich jak C, C++ czy
językach programowania z silną kontrolą typów, takich jak C, C++ czy
Java definiując funkcję musimy jednak podać typy przekazywanych
Java definiując funkcję musimy jednak podać typy przekazywanych
parametrów oraz typ wartości zwracanej. Oznacza to że dla każdego typu
parametrów oraz typ wartości zwracanej. Oznacza to, że dla każdego typu
argumentów musimy definiować nową funkcję <tt>max</tt>:
argumentów musimy definiować nową funkcję <tt>max</tt>:


    int    max(int a, int b)      {return (a>b)?a:b;};
int    max(int a, int b)      {return (a>b)?a:b;};
    double max(double a,double b) {return (a>b)?a:b;};
double max(double a,double b) {return (a>b)?a:b;};
    string max(string a,string b) {return (a>b)?a:b;};
string max(string a,string b) {return (a>b)?a:b;};
    <small>//<-- skorzystaliśmy tu z dostępnej w C++ możliwości przeładowywania funkcji -->//</small>
<i>skorzystaliśmy tu z dostępnej w C++ możliwości przeładowywania funkcji</i><br>
    main() {
main() {
      cout<< max(7,5)<<end;
  cout<< max(7,5)<<end;
      cout<< max(3.1415,2.71)<<endl;
  cout<< max(3.1415,2.71)<<endl;
      cout<< max("Ania","Basia")<<endl;
  cout<< max("Ania","Basia")<<endl;
    }
}


{{przyklad|[1.2]||}}
{{kotwica|prz.1.2|}}{{przyklad|1.2||}}


Takie powtarzanie kodu poza oczywistym zwiększeniem nakładu pracy ma
Takie powtarzanie kodu, poza oczywistym zwiększeniem nakładu pracy, ma
inne niepożądane efekty związane z trudnością zapewnienia
inne niepożądane efekty, związane z trudnością zapewnienia
synchronizacji kodu każdej z funkcji. Jeśli np. zauważymy błąd w
synchronizacji kodu każdej z funkcji. Jeśli np. zauważymy błąd w
kodzie to musimy go poprawić w kilku miejscach. To samo dotyczy
kodzie, to musimy go poprawić w kilku miejscach. To samo dotyczy
optymalizacji kodu. W powyższym przykładzie kod jest wyjątkowo prosty,
optymalizacji kodu. W powyższym przykładzie kod jest wyjątkowo prosty,
ale taki sam problem dotyczy np. funkcji sortujących. Rozważmy prosty
ale taki sam problem dotyczy np. funkcji sortujących. Rozważmy prosty
algorytm sortowania bąbelkowego:
algorytm sortowania bąbelkowego:


    inline void swap(double &a,double &b) {
inline void swap(double &a,double &b) {
    double  tmp<nowiki>=</nowiki>a;a<nowiki>=</nowiki>b;b<nowiki>=</nowiki>tmp;
double  tmp<nowiki>=</nowiki>a;a<nowiki>=</nowiki>b;b<nowiki>=</nowiki>tmp;
    }
}
    void buble_sort(double *data,int N) {
void buble_sort(double *data,int N) {
    for(int i<nowiki>=</nowiki>n-1;i>0;--i)
for(int i<nowiki>=</nowiki>n-1;i>0;--i)
            for(int j<nowiki>=</nowiki>0; j < i;++j)  
        for(int j<nowiki>=</nowiki>0; j < i;++j)  
                    if(data[j]>data[j+1])
                if(data[j]>data[j+1])
                            swap(data[j],data[j+1]);
                        swap(data[j],data[j+1]);
    }
}


Powyższa funkcja sortuje tablicę zawierającą wartości typu <tt>double</tt>
Powyższa funkcja sortuje tablicę zawierającą wartości typu <tt>double</tt>,
ale widać że znów kod będzie identyczny jeśli zamiast <tt>double</tt>
ale widać, że znów kod będzie identyczny, jeśli zamiast <tt>double</tt>
użyjemy dowolnego innego typu którego wartości możemy porównywać za
użyjemy dowolnego innego typu, którego wartości możemy porównywać za
pomocą funkcji <tt>operator>()</tt> i dla którego zdefiniowany jest
pomocą funkcji <tt>operator>()</tt> i dla którego zdefiniowany jest
operator przypisania. Co więcej kod nie zmieni się jeśli zamiast
operator przypisania. Co więcej, kod nie zmieni się jeśli zamiast
tablicy użyjemy dowolnej innej struktury danych umożliwiającej
tablicy użyjemy dowolnej innej struktury danych, umożliwiającej
indeksowany dostęp do swoich składowych np. <tt>std::vector</tt> z
indeksowany dostęp do swoich składowych, np. <tt>std::vector</tt> ze
Standardowej Biblioteki Szablonów STL. W tym przypadku kod jest już
Standardowej Biblioteki Szablonów STL. W tym przypadku kod jest już
bardziej skomplikowany i kłopoty związane z jego powielaniem będą
bardziej skomplikowany i kłopoty związane z jego powielaniem będą
większe.  Przykłady takie można mnożyć istnieje bowiem wiele takich
większe.  Przykłady takie można mnożyć, istnieje bowiem wiele takich
funkcji czy ''algorytmów uogólnionych''. Ich kod może być znacznie
funkcji czy ''algorytmów uogólnionych''. Ich kod może być znacznie
bardziej skomplikowany niż podanych przykładach a zależność od typy
bardziej skomplikowany niż w podanych przykładach, a zależność od typu
argumentów nie musi ograniczać się do sygnatury ale występować również
argumentów nie musi ograniczać się do sygnatury, ale występować również
we wnętrzu funkcji jak np. w przypadku zmiennej <tt>tmp</tt> w funkcji
we wnętrzu funkcji, jak np. w przypadku zmiennej <tt>tmp</tt> w funkcji
<tt>swap</tt>.  Powielanie takiego kodu dla różnych typów parametrów może
<tt>swap</tt>.  Powielanie takiego kodu dla różnych typów parametrów może
łatwo prowadzić do błędów, utrudnia ich wykrywanie, a konieczność
łatwo prowadzić do błędów, utrudnia ich wykrywanie, a konieczność
Linia 75: Linia 75:
===Funkcje uogólnione bez szablonów===
===Funkcje uogólnione bez szablonów===


Jak radzili, a właściwie jak radzą sobie programiści bez możliwościa
Jak radzili, a właściwie jak radzą sobie programiści bez możliwości
skorzystania z szablonów?  
skorzystania z szablonów?  
Tradycyjne sposoby rozwiązywania tego typu problemów to między innymi
Tradycyjne sposoby rozwiązywania tego typu problemów to między innymi
makra:
makra:


    #define max(a,b) ( (a>b)?a:b) )  
<Source>
 
#define max(a,b) ( (a>b)?a:b) )  
lub używanie wskaźników typów ogólnych takich jak <tt>void *</tt>
</Source>
lub używanie wskaźników typów ogólnych, takich jak <tt>void *</tt>,
jak np. w funkcji <tt>qsort</tt> ze standardowej biblioteki C:   
jak np. w funkcji <tt>qsort</tt> ze standardowej biblioteki C:   
 
<Source>
    void qsort(void *base, size_t nmemb, size_t size,
void qsort(void *base, size_t nmemb, size_t size,
                int(*compar)(const void *, const void *));  
          int(*compar)(const void *, const void *));  
 
</Source>
Mimo iż użyteczne żadne z
Mimo iż użyteczne, żadne z
tych rozwiązań nie można uznać za wystarczająco ogólne i bezpieczne.
tych rozwiązań nie może zostać uznane za wystarczająco ogólne i bezpieczne.


Można się również pokusić o próbę rozwiązania tego problemu za pomocą
Można się również pokusić o próbę rozwiązania tego problemu za pomocą
mechanizmów programowania obiektowego.  W sumie jest to bardziej
mechanizmów programowania obiektowego.  W sumie jest to bardziej
wyrafinowna odmiana rzutowania na <tt>void *</tt>.  Polega  na
wyrafinowana odmiana rzutowania na <tt>void *</tt>.  Polega  na
zdefiniowaniu ogólnego typu dla obiektów które mogą być porównywane:
zdefiniowaniu ogólnego typu dla obiektów, które mogą być porównywane:
<Source>
class GreaterThanComparable {
public:
  virtual bool operator>(const GreaterThanComparable &) const = 0;
};
</Source>
następnie zdefiniowaniu funkcji <tt>max</tt> w postaci:  


    class GreaterThenComparable {
<Source>
    virtual bool
const GreaterThanComparable &
    operator>(const GreaterThenComparable &a,  
max(const GreaterThanComparable &a,  
              const GreaterThenComparable &b) <nowiki>=</nowiki> 0;
    const GreaterThanComparable &b) {
    }
      return (a>b)? a:b;
    }
</Source>
([[media:Max_oop.cpp| Źródło: max_oop.cpp]])


następnie zdefiniowanie funkcji <tt>max</tt> w postaci:  
i używaniu jej np. w następujący sposób:


    const GreaterThenComparable &
<Source>
    max(const GreaterThenComparable &a,
class Int:public GreaterThanComparable {
        const GreaterThenComparable &b) {  
  int val;
          return (a>b)? a:b;  
public: Int(int i <nowiki>=</nowiki> 0):val(i) {};
        }  
  operator int() {return val;};
  virtual bool
  operator>(const GreaterThanComparable &b) const {
    return val > static_cast<const Int&>(b).val;
  }  
};


[http://osilek.mimuw.edu.pl/images/c/c6/Max_oop.cpp Przykład]
main() {
 
  Int a(1),b(2);
i używaniu jej np. w następujący sposób
  Int c;
 
  c = (const Int &)::max(a,b);
    class Int:public GreaterThenComparable {
  cout<<(int)c<<endl;
      int val;
}
      public: Int(int i <nowiki>=</nowiki> 0):val(i) {};
</Source>
      operator int() {return val;};
([[media:Max_oop.cpp | Źródło: max_oop.cpp]])
      virtual bool
      operator>(const GreaterThenComparable &b) const {
        return val > static_cast<const Int&>(b).val;
      }
      };
 
    main() {
    Int a(1),b(2);
    Int c;
    c <nowiki>=</nowiki> (const Int &)::max(a,b);
    cout<<(int)c<<endl;
    }


[http://osilek.mimuw.edu.pl/images/c/c6/Max_oop.cpp Przykład]
Widać więc wyraźnie, że to podejście wymaga sporego nakładu pracy, a
 
Widać więc wyraźnie że to podejście wymaga sporego nakładu pracy a
więc w szczególności w przypadku tak prostej funkcji jak <tt>max</tt> jest
więc w szczególności w przypadku tak prostej funkcji jak <tt>max</tt> jest
wysoce niepraktyczne. Ogólnie rzecz biorąc ma ono następujące wady
wysoce niepraktyczne. Ogólnie rzecz biorąc ma ono następujące wady:
 
1. Wymaga dziedziczenia z abstrakcyjnej klasy bazowej
<tt>GreaterThenComparable</tt>, czyli może być zastosowane tylko do typów
zdefiniowanych przez nas.  Inne typy w tym typy wbudowane wymagają
kopertowania w klasie opakowującej takiej jak klasa <tt>Int</tt> w powyższym
przykładzie.
 
2. Ponieważ potrzebujemy polimorfizmu funkcja <tt>operator>()</tt> musi
być funkcją wirtualną, a więc musi być  musi być funkcją składową klasy i
nie może być typu <tt>inline</tt>. W przypadku tak prostych funkcji
niemożność rozwinięcia ich w miejscu wywołania może prowadzić do
dużych narzutów w czasie wykonania.


3. Funkcja <tt>max</tt> zwraca zawsze referencje do <tt>GreaterThenComparable</tt>
# Wymaga dziedziczenia z abstrakcyjnej klasy bazowej <tt>GreaterThanComparable</tt>, czyli może być zastosowane tylko do typów zdefiniowanych przez nas. Inne typy, w tym typy wbudowane, wymagają kopertowania w klasie opakowującej, takiej jak klasa <tt>Int</tt> w powyższym przykładzie.
więc konieczne jest rzutowanie na typ wynikowy (tu <tt>Int</tt>).
# Ponieważ potrzebujemy polimorfizmu funkcja <tt>operator>()</tt> musi być funkcją wirtualną, a więc musi być funkcją składową klasy i nie może być typu <tt>inline</tt>. W przypadku tak prostych funkcji niemożność rozwinięcia ich w miejscu wywołania może prowadzić do dużych narzutów w czasie wykonania.
# Funkcja <tt>max</tt> zwraca zawsze referencje do <tt>GreaterThanComparable</tt>, więc konieczne jest rzutowanie na typ wynikowy (tu <tt>Int</tt>).


===Szablony funkcji===
===Szablony funkcji===


Widać, że podejście obiektowe nie nadaje się najlepiej do rozwiązywania
Widać, że podejście obiektowe nie nadaje się najlepiej do rozwiązywania
tego szczególnego problemu powielania kodu: Dlatego w C++ wprowadzono
tego szczególnego problemu powielania kodu. Dlatego w C++ wprowadzono
nowy mechanizm: szablony.  Szablony zezwalają na definiowanie całych
nowy mechanizm: szablony.  Szablony zezwalają na definiowanie całych
rodzin funkcji które następnie mogą być używane dla różnych typów
rodzin funkcji, które następnie mogą być używane dla różnych typów
argumentów.
argumentów.


Definicja szablonu funkcji <tt>max</tt> odpowiadającej definicji
Definicja szablonu funkcji <tt>max</tt>, odpowiadającej definicji [[#prz.1.1|1.1]] wygląda następująco:
[[##ex:max-int|Uzupelnic ex:max-int|]] wygląda następująco:


    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;};  
[http://osilek.mimuw.edu.pl/images/c/c4/Max_template.cpp Przykład]
</Source>
([[media:Max_template.cpp | Źródło: max_template.cpp]])


Przyjrzyjmy się jej z bliska. Wyrażenie <tt>template<typename T></tt>
Przyjrzyjmy się jej z bliska. Wyrażenie <tt>template<typename T></tt>
oznacza że mamy do czynienia z szablonem który posiada jeden parametr
oznacza, że mamy do czynienia z szablonem, który posiada jeden parametr
formalny nazwany <tt>T</tt>. Słowo kluczowe <tt>typename</tt> oznacza że
formalny nazwany <tt>T</tt>. Słowo kluczowe <tt>typename</tt> oznacza, że
parametr ten jest typem (nazwą typu). Zamiast słowa <tt>typename</tt>
parametr ten jest typem (nazwą typu). Zamiast słowa <tt>typename</tt>
możmy użyć słowa kluczowego <tt>class</tt>.  Nazwa tego parametru może być
możmy użyć słowa kluczowego <tt>class</tt>.  Nazwa tego parametru może być
następnie wykorzystywana w definicji funkcji w miejscach gdzie
następnie wykorzystywana w definicji funkcji w miejscach, gdzie
spodziewamy się nazwy typu. I tak powyższe wyrażenie definiuje funkcje
spodziewamy się nazwy typu. I tak powyższe wyrażenie definiuje funkcję
<tt>max</tt> która przyjmuje dwa argumenty typu <tt>T</tt> i zwraca wartość
<tt>max</tt>, która przyjmuje dwa argumenty typu <tt>T</tt> i zwraca wartość
typu <tt>T</tt> będącą wartością większego z dwu argumentów. Typ <tt>T</tt>
typu <tt>T</tt>, będącą wartością większego z dwu argumentów. Typ <tt>T</tt>
jest na razie nie wyspecyfikowany. W tym sensie szablon definiuje całą
jest na razie niewyspecyfikowany. W tym sensie szablon definiuje całą
rodzinę funkcji. Konkretną funkcję z tej rodziny wybieramy poprzez
rodzinę funkcji. Konkretną funkcję z tej rodziny wybieramy poprzez
podstawienie ze formalny parametr <tt>T</tt> konkretnego typu będącego
podstawienie za formalny parametr <tt>T</tt> konkretnego typu będącego
argumentem szablonu. Takie podstawienie nazywamy konkretyzacją
argumentem szablonu. Takie podstawienie nazywamy konkretyzacją
szablonu. Argument szablonu umieszczamy w nawiasach ostrych za nazwą
szablonu. Argument szablonu umieszczamy w nawiasach ostrych za nazwą
szablonu (w praktyce można uniknąć konieczności jawnej specyfikacji
szablonu (w praktyce można uniknąć konieczności jawnej specyfikacji
argumentów szablonu, opiszemy to w następnych częściach wykładu):
argumentów szablonu, opiszemy to w następnych częściach wykładu):
 
<Source>
    int i,j,k;
int i,j,k;
    k<nowiki>=</nowiki>max<int>(i,j);
k=max<int>(i,j);
 
</Source>
Takie użycie szablonu spowoduje wygenerowanie identycznej funkcji jak
Takie użycie szablonu spowoduje wygenerowanie identycznej funkcji jak [[#prz.1.1|1.1]]. W powyższym przypadku za <tt>T</tt> podstawiamy <tt>int</tt>. Oczywiście możemy podstawić za <tt>T</tt> dowolny typ i używając szablonów program [[#prz.1.2|1.2]] można zapisać następująco:
[[##ex:max-int|Uzupelnic ex:max-int|]]. W powyższym przypadku za <tt>T</tt> podstawiamy <tt>int</tt>. Oczywiście możemy podstawić za <tt>T</tt> dowolny typ i używając szablonów program
<Source>
[[##max2|Uzupelnic max2|]] można zapisać następująco:
template<typename T> T max(T a,T b) {return (a>b)?a:b;}
 
main() {
    template<typename T> T max(T a,T b) {return (a>b)?a:b;}
  cout<<::max<int>(7,5)<<endl;
 
  cout<<::max<double>(3.1415,2.71)<<endl;
    main() {
  cout<<::max<string>("Ania","Basia")<<endl;
    cout<<::max<int>(7,5)<<endl;
}
    cout<<::max<double>(3.1415,2.71)<<endl;
</Source>
    cout<<::max<string>("Ania","Basia")<<endl;
([[media:Max_template.cpp | Źródło: max_template.cpp]])
    }
 
[http://osilek.mimuw.edu.pl/images/c/c4/Max_template.cpp Przykład]


W powyższym kodzie użyliśmy konstrukcji <tt>::max(a,b)</tt>. Dwa dwukropki
W powyższym kodzie użyliśmy konstrukcji <tt>::max(a,b)</tt>. Dwa dwukropki
oznaczają to że używamy funkcji <tt>max</tt> zdefiniowanej w ogólnej
oznaczają, że używamy funkcji <tt>max</tt> zdefiniowanej w ogólnej
przestrzeni nazw. Jest to konieczne aby kod się skompilował ponieważ
przestrzeni nazw. Jest to konieczne aby kod się skompilował, ponieważ
szablon <tt>max</tt> istnieje już w standardowej przestrzeni nazw
szablon <tt>max</tt> istnieje już w standardowej przestrzeni nazw
<tt>std</tt>. W dalszej części wykładu będziemy te podwójne dwukropki pomijać.
<tt>std</tt>. W dalszej części wykładu będziemy te podwójne dwukropki pomijać.


Oczywiście istnieją typy których podstawienia spowoduje błedy
Oczywiście istnieją typy których podstawienie spowoduje błędy
kompilacjie np.
kompilacji, np.
 
    complex<double> c1,c2;
    max<complex<double> >(c1,c2);<small>//<-- brak operatora > -->//</small>


[http://osilek.mimuw.edu.pl/images/c/c4/Max_template.cpp Przykład]
<Source>
complex<double> c1,c2;
max<complex<double> >(c1,c2); //brak operatora >
</Source>
([[media:Max_template.cpp | Źródło: max_template.cpp]])


lub
lub
<Source>
class X {
private:
  X(const X &){};
};
X a,b;
max<X>(a,b); //prywatny (niewidoczny) konstruktor kopiujący
</Source>


    X {
([[media:Max_template.cpp | Źródło: max_template.cpp]])
    private:
    X(const X &){};
    };


    X a,b;
Ogólnie rzecz biorąc, każdy szablon definiuje pewną klasę typów, które
    max<X>(a,b);<small>//<-- prywatny (niewidoczny) konstruktor kopiujący -->//</small>
 
[http://osilek.mimuw.edu.pl/images/c/c4/Max_template.cpp Przykład]
 
Ogólnie rzecz biorąc każdy szablon definiuje pewną klasę typów które
mogą zostać podstawione jako jego argumenty.
mogą zostać podstawione jako jego argumenty.


===Dedukcja argumentów szablonu===
===Dedukcja argumentów szablonu===


Użyteczność szablonów funkcji zwiększa istotnie fakt że argumenty szablonu nie muszą być podawane jawnie. Kompilator może je wydedukować z argumentów funkcji. Tak więc zamiast  
Użyteczność szablonów funkcji zwiększa istotnie fakt, że argumenty szablonu nie muszą być podawane jawnie. Kompilator może je wydedukować z argumentów funkcji. Tak więc zamiast  


    int i,j,k;
int i,j,k;
    k<nowiki>=</nowiki>max<int>(i,j);
k<nowiki>=</nowiki>max<int>(i,j);


możemy napisać
możemy napisać


    int i,j,k;
int i,j,k;
    k<nowiki>=</nowiki>max(i,j);
k<nowiki>=</nowiki>max(i,j);


i kompilator zauważy że tylko podstawienie za  <tt>int</tt>-a za <tt>T</tt> umożliwi
i kompilator zauważy, że tylko podstawienie <tt>int</tt>-a za <tt>T</tt> umożliwi
dopasowanie sygnatury funkcji do parametrów jej wywołania i
dopasowanie sygnatury funkcji do parametrów jej wywołania i
automatycznie dokona odpowiedniej konkretyzacji.
automatycznie dokona odpowiedniej konkretyzacji.


  Może się zdarzyć że podamy takie argumenty funkcji że dopasowanie
Może się zdarzyć, że podamy takie argumenty funkcji, że dopasowanie
argumentów wzorca będzie niemożliwe, otrzymamy wtedy błąd kompilacji.
argumentów wzorca będzie niemożliwe, otrzymamy wtedy błąd kompilacji.
Trzeba pamiętać że mechanizm automatycznego dopasowywania argumentów
Trzeba pamiętać, że mechanizm automatycznego dopasowywania argumentów
szablonu powoduje wyłączenie automatycznej konwersji argumentów
szablonu powoduje wyłączenie automatycznej konwersji argumentów
funkcji. Podanie jawnie argumentów szablonu (w nawiasach ostrych za
funkcji. Podanie jawnie argumentów szablonu (w nawiasach ostrych za
nazwą szablonu) jednoznacznie określa sygnaturę funkcji a więc
nazwą szablonu) jednoznacznie określa sygnaturę funkcji, a więc
umożliwia automatyczną konwersję typów.  Ilustruje to poniższy kod:
umożliwia automatyczną konwersję typów.  Ilustruje to poniższy kod:


    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;}
main() {
main() {
cout<<::max(3.14,2)<<endl;
  cout<<::max(3.14,2)<<endl;
/* błąd: kompilator nie jest w stanie wydedukowac
  // błąd: kompilator nie jest w stanie wydedukowac argumentu szablonu, bo typy  
argumentu szablony bo typy argumentow
  // argumentów (double,int) nie pasują do (T,T)
(int,double) nie pasuja do (T,T)
*/


cout<<::max<int>(3.14,2)<<endl;
  cout<<::max<int>(3.14,2)<<endl;
/*podajac argument jawnie wymuszamy sygnature
  // podając argument jawnie wymuszamy sygnaturę int max(int,int), a co za tym  
int max(int,int)  
  // idzie automatyczną konwersję argumentu 1 do int-a
a co za tym idzie automatyczna konwersje 
argumentu 1 do int-a
*/


cout<<::max<double>(3.14,2)<<endl;
  cout<<::max<double>(3.14,2)<<endl;
/*
  // podając argument szablonu jawnie wymuszamy sygnaturę
podajac argument szablonu jawnie wymuszamy sygnature
  // double max(double,double)
<tt>double max(double,double)</tt>
  // a co za tym idzie automatyczną konwersję argumentu 2 do double-a
a co za tym idzie automatyczna konwersje
argumentu 2 do double-a
  int i;
*/
  cout<<::max<int *>(&i,i)<<endl;  
 
  //błąd: nie istnieje konwersja z typu int na int*
int i;
</Source>
cout<<::max<int *>(&i,i)<<endl;  
([[media:Max_template.cpp | Źródło: max_template.cpp]])
/*błąd: nie istnieje konwersja z typu int na int*/
/*{mod01/code/max_template.cpp}
}


Może warto zauważyć że automatyczna dedukcja parametrów szablonu jest
Może warto zauważyć, że automatyczna dedukcja parametrów szablonu jest
możliwa tylko wtedy jeśli parametry wywołania funkcji w jakiś sposób
możliwa tylko wtedy, jeśli parametry wywołania funkcji w jakiś sposób
zależą od parametrów szablonu. Jeśli tej zależności nie ma, z przyczyn
zależą od parametrów szablonu. Jeśli tej zależności nie ma, z przyczyn
oczywistych dedukcja nie jest możliwa i trzeba parametry podawać
oczywistych dedukcja nie jest możliwa i trzeba parametry podawać
jawnie. Wtedy istotna jest kolejność parametrów na liście. Jeżeli
jawnie. Wtedy istotna jest kolejność parametrów na liście. Jeżeli
parametry których nie da się wydedukować umieścimy jako pierwsze,
parametry, których nie da się wydedukować, umieścimy jako pierwsze,
wystarczy że tylko je podamy jawnie a kompilator wydedukuje resztę.
wystarczy, że tylko je podamy jawnie, a kompilator wydedukuje resztę.
Ilustruje to poniższy kod:
Ilustruje to poniższy kod:


template<typename T,typename U> T convert(U u) {
template<typename T,typename U> T convert(U u) {
return (T)u;
return (T)u;
};
};
template<typename U,typename T> T inv_convert(U u) {
template<typename U,typename T> T inv_convert(U u) {
return (T)u;
return (T)u;
};
};
/* fukcje różnią się tylko kolejnością parametrów szablonu*/
<i>fukcje różnią się tylko kolejnością parametrów szablonu</i><br>
main() {
main() {
cout<<convert(33)<<endl;
cout<<convert(33)<<endl;
/* bład: kompilator nie jest wstanie wydedukować pierwszego parametru  
<i>błąd: kompilator nie jest w stanie wydedukować pierwszego parametru  
szablonu,  bo  nie zależy on od parametru wywołania funkcji*/  
szablonu,  bo  nie zależy on od parametru wywołania funkcji</i><br>
cout<<convert<char>(33)<<endl;
cout<<convert<char>(33)<<endl;
/* w porządku: podajemy jawnie argument T, kompilator sam dedukuje  
<i>w porządku: podajemy jawnie argument T, kompilator sam dedukuje  
argument U z typu argumentu wywołania funkcji */  
argument U z typu argumentu wywołania funkcji</i><br>
cout<<inv_convert<char>('a')<<endl;  
cout<<inv_convert<char>('a')<<endl;  
/*błąd: podajemy jawnie argument odpowiadający paremetrowi U.  
<i>błąd: podajemy jawnie argument odpowiadający parametrowi U.  
Kompilator nie jest w stanie wydedukować argumentu T,
Kompilator nie jest w stanie wydedukować argumentu T, bo nie zależy on od argumentu  
bo nie zależy on od argumentu wywołania funkcji
wywołania funkcji</i><br>
*/  
cout<<inv_convert<int,char>(33)<<endl;
cout<<inv_convert<int,char>(33)<<endl;
<i>w porządku: podajemy jawnie oba argumenty szablonu</i>
/* w porządku: podajemy jawnie oba argumenty szablonu*/
}
}
 
/*{mod01/code/convert.cpp}
([[media:Convert.cpp | Źródło: convert.cpp]])


===Używanie szablonów===
===Używanie szablonów===
Linia 322: Linia 303:
przykładach. W językach C i C++ zwykle rozdzielamy deklarację funkcji
przykładach. W językach C i C++ zwykle rozdzielamy deklarację funkcji
od jej definicji i zwyczajowo umieszczamy deklarację w plikach
od jej definicji i zwyczajowo umieszczamy deklarację w plikach
nagłówkowych !*.h! a definicję w plikach źródłowych !*.c!,
nagłówkowych <tt>*.h</tt>, a definicję w plikach źródłowych <tt>*.c</tt>,
!*.cpp! itp. Pliki nagłówkowe są w czasie kompilacji włączane do
<tt>*.cpp</tt> itp. Pliki nagłówkowe są w czasie kompilacji włączane do
plików w których chcemy korzystać z danej funkcji a pliki źródłowe są
plików, w których chcemy korzystać z danej funkcji, a pliki źródłowe są
pojedynczo kompilowane do plików “obiektowych” !*.o!. Następnie
pojedynczo kompilowane do plików “obiektowych” <tt>*.o</tt>. Następnie
pliki obiektowe są łączone w jeden plik wynikowy (zob
pliki obiektowe są łączone w jeden plik wynikowy (zob. [[#rys.1.1|rysunek 1.1]]). W pliku
rysunek&nbsp;[[##fig:kod1|Uzupelnic fig:kod1|]] i {linking.html}{kod źródłówy}). W pliku
korzystającym z danej funkcji nie musimy więc znać jej definicji, a
korzystającym z danej funkcji nie musimy więc znać jej definicji a
tylko deklarację.  Na podstawie nazwy funkcji konsolidator powiąże
tylko deklarację.  Na podstawie nazwy funkcji konsolidator powiąże
wywołanie funkcji z jej implementacją znajdującą się w innym pliku
wywołanie funkcji z jej implementacją znajdującą się w innym pliku
obiektowym. W ten sposób tylko zmiana deklaracji funkcji wymaga
obiektowym. W ten sposób tylko zmiana deklaracji funkcji wymaga
rekompilacji plików w których z niej korzystamy, a zmiana definicji
rekompilacji plików, w których z niej korzystamy, a zmiana definicji
wymaga jedynie rekompilacji pliku w którym dana funkcja jest
wymaga jedynie rekompilacji pliku, w którym dana funkcja jest
zdefiniowana.
zdefiniowana.
[p]


[height<nowiki>=</nowiki>,angle<nowiki>=</nowiki>90]{mod01/graphics/kod1.eps}
{{kotwica|rys.1.1|}}<center>
 
<div class="thumb"><div style="width:750px;">
{Przykład organizacji kodu C++ w przypadku
[[Grafika:Cpp-1-kod1.png]]
użycia zwykłych funkcji.}
<div.thumbcaption>Rysunek 1.1. Przykład organizacji kodu C++ w przypadku
użycia zwykłych funkcji.</div>
</div></div>
</center>


Taka organizacja umożliwia przestrzeganie "reguły jednej definicji"
Taka organizacja umożliwia przestrzeganie "reguły jednej definicji"
(one definition rule) wymaganej przez C++.  
(one definition rule), wymaganej przez C++.  
Osobom nieobeznanym z programowaniem w C/C++ zwracam uwagę na konstrukcje
Osobom nieobeznanym z programowaniem w C/C++ zwracam uwagę na konstrukcję


#ifndef _nazwa_pliku_
#ifndef _nazwa_pliku_
#define _nazwa_pliku_
#define _nazwa_pliku_
...
...
#endif
#endif


uniemożliwiajacą podwójne włączenie tego pliku do jednej jednostki
uniemożliwiajacą podwójne włączenie tego pliku do jednej jednostki
translacyjnej.
translacyjnej.


Podobne podejście do kompilacje szablonów się nie powiedzie (zob
Podobne podejście do kompilacje szablonów się nie powiedzie (zob. [[#rys.1.2|rysunek 1.2]]).
rysunek&nbsp;[[##fig:kod2|Uzupelnic fig:kod2|]] i {linking_template.html}{kod źródłówy}).
Powodem jest fakt, że w trakcie kompilacji pliku <tt>utils.cpp</tt>
Powodem jest fakt że w trakcie kompilacji pliku <tt>utils.cpp</tt>
kompilator nie wie jeszcze, że potrzebna będzie funkcja <tt>max<int></tt>,
kompilator nie wie jeszcze że potrzebna będzie funkcja !max<int>!,
wobec czego nie generuje kodu żadnej funkcji, a jedynie sprawdza
wobec czego nie generuje kodu żadnej funkcji a jedynie sprawdza
poprawność gramatyczną szablonu. Z kolei podczas kompilacji pliku
poprawność gramatyczną szablonu. Z kolei podczas kompilacji pliku
<tt>main.cpp</tt> kompilator już wie, że ma skonkretyzować szablon dla <tt>T <nowiki>=</nowiki> int</tt>,
main.cpp kompilator już wie że ma skonkretyzować szablon dla T <nowiki>=</nowiki> int
ale nie ma dostępu do kodu szablonu.
ale nie ma dostępu do kodu szablonu.
[p]


[height<nowiki>=</nowiki>,angle<nowiki>=</nowiki>90]{mod01/graphics/kod2.eps}
{{kotwica|rys.1.2|}}<center>


{Przykład błędnej organizacji kodu w przypadku
[[File:cpp-1-kod2.svg|750x500px|thumb|center|Rysunek 1.2. Przykład błędnej organizacji kodu w przypadku
użycia szablonów}
użycia szablonów]]


Istnieją różne rozwiązania tego problemu. Najprościej chyba jest
Istnieją różne rozwiązania tego problemu. Najprościej chyba jest
zauważyć że opisane zachowanie jest analogiczne do zachowania podczas
zauważyć, że opisane zachowanie jest analogiczne do zachowania podczas
kompilacji funkcji rozwijanych w miejscu wywołania (!inline!)
kompilacji funkcji rozwijanych w miejscu wywołania (<tt>inline</tt>),
których definicja również musi być dostępna w czasie kompilacji.
których definicja również musi być dostępna w czasie kompilacji.
Podobnie więc jak w tym przypadku możemy zamieścić wszystkie
Podobnie więc jak w tym przypadku możemy zamieścić wszystkie
deklaracje i definicje szablonów w pliknu nagłówkowym właczanym do
deklaracje i definicje szablonów w pliknu nagłówkowym, włączanym do
wplików w ktorych z tych szablonów korzystamy (zob
plików, w ktorych z tych szablonów korzystamy (zob. [[#rys.1.3|rysunek 1.3]]). Podobnie
rysunek&nbsp;[[##fig:kod3|Uzupelnic fig:kod3|]] i {include.html}{kod źródłówy}). Podobnie
jak w przypadku funkcji <tt>inline</tt> reguła jednej definicji zezwala na
jak w przypadku funkcji !inline! reguła jednej definicji zezwala na
powtarzanie definicji/deklaracji szablonów w różnych jednostkach
powtarzanie definicji/deklaracji szablonów w różnych jednostkach
translacyjnych pod warunkiem że są  one identyczne. Stąd konieczność  
translacyjnych, pod warunkiem, że są  one identyczne. Stąd konieczność  
umieszczania ich w plikach nagłówkowych.   
umieszczania ich w plikach nagłówkowych.   
[p]


[height<nowiki>=</nowiki>,angle<nowiki>=</nowiki>90]{mod01/graphics/kod3.eps}
{{kotwica|rys.1.3|}}<center>
[[File:cpp-1-kod3.svg|750x500px|thumb|center|Rysunek 1.3. Przykład organizacji kodu z szablonami, wykorzystującego strategię włączania.]]
</center>


{Przykład organizacji kodu z szablonami, wykorzystującego strategię włączania.}
Ten sposób organizacji pracy z szablonami, nazywany modelem włączenia,
 
jest  najbardziej uniwersalny. Jego główną wadą jestkonieczność
Ten sposób organizacji pracy z szablonami, nazywany modelem włączenia
jest  najbardziej uniwersalny. Jego główną wadą to konieczność
rekompilacji całego kodu korzystającego z szablonów przy każdej
rekompilacji całego kodu korzystającego z szablonów przy każdej
zmianie definicji szablonu.  Również jeśli zmienimy coś w pliku w
zmianie definicji szablonu.  Również jeśli zmienimy coś w pliku, w
którym korzystamy z szablonu to musimy rekompilować cały kod szablonu
którym korzystamy z szablonu, to musimy rekompilować cały kod szablonu
włączony do tego pliku nawet jeśli nie uległ on zmianie. Jeśli się
włączony do tego pliku, nawet jeśli nie uległ on zmianie. Jeśli się
uwzględni fakt że kompilacja szablonu jest bardziej skomplikowana od
uwzględni fakt, że kompilacja szablonu jest bardziej skomplikowana od
kompilacji "zwykłego" kodu to duży projekt intensywnie korzystający z
kompilacji "zwykłego" kodu, to duży projekt intensywnie korzystający z
szablonów może wymagać bardzo długich czasów kompilacji.
szablonów może wymagać bardzo długich czasów kompilacji.


Możemy też w jakiś sposób dać znać kompilatorowi że podczas kompilacji
Możemy też w jakiś sposób dać znać kompilatorowi, że podczas kompilacji
pliku <tt>utils.cpp</tt> powinien wygenerować kod dla funkcji
pliku <tt>utils.cpp</tt> powinien wygenerować kod dla funkcji
!max<int>!. Można to zrobić dodając jawne żądanie konkretyzacji
<tt>max<int></tt>. Można to zrobić dodając jawne żądanie konkretyzacji
szablonu (zob rysunek&nbsp;[[##fig:kod4|Uzupelnic fig:kod4|]] i {explicit.html}{kod źródłówy}):
szablonu (zob. [[#rys.1.4|rysunek 1.4]]):


template<typename T> T max(T a,T b) {return (a>b)?a:b;}
template<typename T> T max(T a,T b) {return (a>b)?a:b;}
template int max<int>(int ,int) ;/*konkretyzacja jawna*/
template int max<int>(int ,int) ; <i>konkretyzacja jawna</i>
/*{explicit.html}{kod źródłowy}


Używając konkretyzacji jawnej musimy pamiętać o dokonaniu
Używając konkretyzacji jawnej musimy pamiętać o dokonaniu
konkretyzacjikażdej używanej funkcji, tak że topodejście się nie
konkretyzacji każdej używanej funkcji, tak że to podejście nie
skaluje zbyt dobrze.  Ponadto w przypadku szablonów klas (omawianych w
skaluje się zbyt dobrze.  Ponadto w przypadku szablonów klas (omawianych w
następnym module) konkretyzacja jawna pociąga za sobą konkretyzację
następnym module) konkretyzacja jawna pociąga za sobą konkretyzację
wszystkich metod danej klasy a konkretyzacja “na żądanie” jedynie
wszystkich metod danej klasy, a konkretyzacja “na żądanie” - jedynie
tych używanych w programie.
tych używanych w programie.
[p]


[height<nowiki>=</nowiki>,angle<nowiki>=</nowiki>90]{mod01/graphics/kod4.eps}
{{kotwica|rys.1.4|}}<center>
 
<div class="thumb"><div style="width:804px;">
{Przykład organizacji kodu z szablonami,
[[Grafika:Cpp-1-kod4.png]]
wykorzystującego jawną konkretyzację}
<div.thumbcaption>Rysunek 1.4. Przykład organizacji kodu z szablonami,
wykorzystującego jawną konkretyzację.</div>
</div></div>
</center>


===Pozatypowe parametry szablonów ===
===Pozatypowe parametry szablonów ===


Poza parametrami określającymi typ, takimi jak parametr !T! w
Poza parametrami określającymi typ, takimi jak parametr <tt>T</tt> w
dotychczasowych przykładach szablony funkcji mogą przyjmować również
dotychczasowych przykładach, szablony funkcji mogą przyjmować również
parametry innego rodzaju. Obecnie mogą ta być inne szablony, co omówię w
parametry innego rodzaju. Obecnie mogą to być inne szablony, co omówię w
następnym podrozdziale lub parametry określające nie typ ale wartości.
następnym podrozdziale lub parametry określające nie typ, ale wartości.
Jak na razie (w obecnym standardzie) te wartości nie mogą być dowolne
Jak na razie (w obecnym standardzie) te wartości nie mogą być dowolne,
ale muszą mieć jeden z poniższych typów:
ale muszą mieć jeden z poniższych typów:


# typ całkowitoliczbowy bądź typ wyliczeniowy
# typ całkowitoliczbowy bądź typ wyliczeniowy
# typ wskaźnikowy  
# typ wskaźnikowy  
# typ referencyjny.


# typ referencyjny
Takie parametry  określające wartość nazywamy parametrami pozatypowymi.
 
Takie parametry  określajace wartość nazywamy parametrami pozatypowymi.
W praktyce z parametrów pozatypowych najczęściej używa się
W praktyce z parametrów pozatypowych najczęściej używa się
parametrów typu całkowitoliczbowego. Np.
parametrów typu całkowitoliczbowego. Np.


template<size_t N,typename T> T dot_product(T *a,T *b) {
template<size_t N,typename T> T dot_product(T *a,T *b) {
T total<nowiki>=</nowiki>0.0;
        T total<nowiki>=</nowiki>0.0;
for(size_t i<nowiki>=</nowiki>0;i<N;++i)
        for(size_t i<nowiki>=</nowiki>0;i<N;++i)
total +<nowiki>=</nowiki> a[i]*b[i] ;
                total +<nowiki>=</nowiki> a[i]*b[i] ;<br>
return total;
};


return total;
([[media:Dot_product.cpp | Źródło: dot_product.cpp]])
};
/*{mod01/code/dot_product.cpp}*/


Po raz drugi zwracam uwagę na kolejność parametrów szablonu na liście
Po raz drugi zwracam uwagę na kolejność parametrów szablonu na liście
parametrów. Dzięki temu że niededukowalny parametr !N! jest na
parametrów. Dzięki temu, że niededukowalny parametr <tt>N</tt> jest na
pierwszym miejscu wystarczy podać jawnie tylko jego, drugi parametr
pierwszym miejscu wystarczy podać jawnie tylko jego, drugi parametr
typu !T! zostanie sam automatycznie wydedukowany na podstawie
typu <tt>T</tt> zostanie sam automatycznie wydedukowany na podstawie
przekazanych argumentów wywołania funkcji:
przekazanych argumentów wywołania funkcji:


main() {
main() {
double x[3],y[3];
double x[3],y[3];
dot_product<3>(x,y);
dot_product<3>(x,y);
}
}
/*{mod01/code/dot_product.cpp}*/
 
([[media:Dot_product.cpp | Źródło: dot_product.cpp]])


Parametry pozatypowe są zresztą "ciężko dedukowalne". Właściwie
Parametry pozatypowe są zresztą "ciężko dedukowalne". Właściwie
jedynym sposobem na przekazania wartości stałej poprzez typ argumentu
jedynym sposobem na przekazania wartości stałej poprzez typ argumentu
wywołania jest skorzystanie z parametrów bedących szablonami
wywołania jest skorzystanie z parametrów będących szablonami
klas (zob. następny podrozdział).  
klas (zob. następny podrozdział).  


Używając pozatypowych parametrów szablonów musimy pamiętać że
Używając pozatypowych parametrów szablonów musimy pamiętać, że
odpowiadające im argumenty muszą być stałymi wyrażeniami czasu
odpowiadające im argumenty muszą być stałymi wyrażeniami czasu
kompilacji.  Stąd jeżeli używamy typów wskaźnikowych muszą to być
kompilacji.  Stąd jeżeli używamy typów wskaźnikowych, muszą to być
wskaźniki do obiektów łączonych zewnętrznie, a nie lokalnych. Ponieważ
wskaźniki do obiektów łączonych zewnętrznie, a nie lokalnych. Ponieważ
jednak jeszcze ani razu nie używałem pozatypowych parametrów szablonów
jednak jeszcze ani razu nie używałem pozatypowych parametrów szablonów
Linia 474: Linia 452:
===Szablony parametrów szablonu===
===Szablony parametrów szablonu===


Jak już wspomniałem w poprzednim podrozdziale parametrami szablonu
Jak już wspomniałem w poprzednim podrozdziale, parametrami szablonu
funkcji mogą być również szablony klas (zob. następny podrozdział).
funkcji mogą być również szablony klas (zob. następny podrozdział).
Szablony parametrów szablonu umożliwiają przekazania nazwy szablonu
Szablony parametrów szablonu umożliwiają przekazanie nazwy szablonu
jako argumentu szablonu funkcji. Więcej o nich napiszę w drugiej
jako argumentu szablonu funkcji. Więcej o nich napiszę w drugiej
części wykładu. Tutaj jako ciekawostkę tylko pokażę w jaki sposób można
części wykładu. Tutaj tylko pokażę jako ciekawostkę w jaki sposób można
dedukować wartości pozatypowych argumentów szablonu.
dedukować wartości pozatypowych argumentów szablonu.


template< template<int N> class  C,int K>
template< template<int N> class  C,int K>
/* taka definicja oznacza że parametr C określa  
<i>taka definicja oznacza, że parametr C określa szablon klasy  
szablon klasy posiadającyjeden parametr tyu <tt>int</tt>.
posiadający jeden parametr typu <tt>int</tt>. Parametr N służy tylko  
Paremetr N służy tylko do definicji szablonu C i nie może być  
do definicji szablonu C i nie może być użyty nigdzie indziej</i>
użyty nigdzie indziej */
void f(C<K>){
void f(C<K>){
  cout<<K<<endl;
cout<<K<<endl;
};<br>
};
template<int N> struct SomeClass {};<br>
main() {
  SomeClass<1>  c1;
  SomeClass<2>  c2;<br>
  f(c1); <i>C<nowiki>=</nowiki>SomeClass K<nowiki>=</nowiki>1</i>
  f(c2); <i>C<nowiki>=</nowiki>SomeClass K<nowiki>=</nowiki>2</i>
}


template<int N> struct SomeClass {};
([[media:Deduce_N.cpp | Źródło: deduce_N.cpp]])
 
main() {
SomeClass<1>  c1;
SomeClass<2>  c2;
 
f(c1);/* C<nowiki>=</nowiki>SomeClass K<nowiki>=</nowiki>1*/
f(c2);/* C<nowiki>=</nowiki>SomeClass K<nowiki>=</nowiki>2*/
}
/*{mod01/code/deduce_N.cpp}*/


===Szablony metod===
===Szablony metod===
Linia 505: Linia 480:
Jak na razie definiowaliśmy szablony zwykłych funkcji. C++ umożliwia
Jak na razie definiowaliśmy szablony zwykłych funkcji. C++ umożliwia
również definiowanie szablonów metod klasy np.:
również definiowanie szablonów metod klasy np.:
 
<Source>
struct Max {
struct Max {
template<typename T> max(T a,T b) {return (a>b)?a:b;}
  template<typename T> T max(T a,T b) {return (a>b)?a:b;}
};
};
main() {
main() {
Max m;
  Max m;
m.max(1,2);
  m.max(1,2);
}
}
/*{mod01/code/max_method.cpp}
</Source>
 
([[media:Max_method.cpp | Źródło: max_method.cpp]])


Szablonów metod składowych dotyczą takie same reguły jak szablonów funkcji.
Szablonów metod składowych dotyczą takie same reguły jak szablonów funkcji.
Linia 523: Linia 499:


Uwagi na początku poprzedniego rozdziału odnoszą się w tej samej
Uwagi na początku poprzedniego rozdziału odnoszą się w tej samej
mierze do klas jak i do funkcji. I tutaj mamy do czynienia z kodem
mierze do klas, jak i do funkcji. I tutaj mamy do czynienia z kodem,
który w niezmienionej postaci musimy powielać dla różnych typów.
który w niezmienionej postaci musimy powielać dla różnych typów.
Sztandarowym przykładem takiego kodu są różnego rodzaju kontenery(pojemniki)
Sztandarowym przykładem takiego kodu są różnego rodzaju kontenery (pojemniki),
czyli obiekty służące do przechowywania innych obiektów. Jest
czyli obiekty służące do przechowywania innych obiektów. Jest
oczywiste że kod kontenera jest w dużej mierze niezależny od typu
oczywiste, że kod kontenera jest w dużej mierze niezależny od typu
obiektów w nim przechowywanych. Jako przykład weźmy
obiektów w nim przechowywanych. Jako przykład weźmy
sobie stos liczb całkowitych. Możliwa definicja klasy stos może
sobie stos liczb całkowitych. Możliwa definicja klasy stos może
Linia 533: Linia 509:
w prawdziwych aplikacjach:
w prawdziwych aplikacjach:


<Source>
class Stack {
class Stack {
private:
private:
 
  int rep[N];
int rep[N];
  size_t top;
_size_t top;
 
public:
public:
static const size_t N<nowiki>=</nowiki>100;
  static const size_t N=100;
Stos_int():_top(0) {};
  Stack():_top(0) {};
 
  void push(int val) {_rep[_top++]=val;}
void push(int val) {_rep[_top++]<nowiki>=</nowiki>val;}
  int pop() {return rep[--top];}
int pop()         {return rep[--top];}
  bool is_empty {return (top==0);}
bool is_empty {return (top<nowiki>=</nowiki><nowiki>=</nowiki>0);}  
 
}
}
 
</Source>
Ewidentnie ten kod będzie identyczny dla stosu obiektów dowolnego
Ewidentnie ten kod będzie identyczny dla stosu obiektów dowolnego
innego typu pod warunkiem że typ ten posiada zdefiniowany
innego typu, pod warunkiem, że typ ten posiada zdefiniowany
!operator<nowiki>=</nowiki>()! i konstruktor kopiujący.
<tt>operator<nowiki>=</nowiki>()</tt> i konstruktor kopiujący.


W celu zaimplementowania kontenerów bez pomocy szablonów możemy
W celu zaimplementowania kontenerów bez pomocy szablonów możemy
probować podobnych sztuczek jak te opisane w poprzednim rozdziale. W
probować podobnych sztuczek jak te opisane w poprzednim rozdziale. W
językach takich jak Java czy Smalltalk które posiadają uniwersalną
językach takich jak Java czy Smalltalk, które posiadają uniwersalną
klasę !Object! z której są dziedziczone wszystkie inne klasy, a nie
klasę <tt>Object</tt>, z której są dziedziczone wszystkie inne klasy, a nie
posiadają (Java już posiada) szablonów, uniwersalne kontenery są
posiadają (Java już posiada) szablonów, uniwersalne kontenery są
implementowane właśnie poprzez rzutowanie na ten ogólny typ.  W
implementowane właśnie poprzez rzutowanie na ten ogólny typ.  W
przypadku C++ nawet to rozwiązanie nie jest praktyczne bo C++ nie
przypadku C++ nawet to rozwiązanie nie jest praktyczne, bo C++ nie
posiada pojedynczej hierarchi klas.
posiada pojedynczej hierarchii klas.


===Szablony klas===
===Szablony klas===
Linia 566: Linia 539:
Rozwiązaniem są znów szablony, tym razem szablony klas. Podobnie jak w
Rozwiązaniem są znów szablony, tym razem szablony klas. Podobnie jak w
przypadku szablonów funkcji, szablon klasy definiuje nam w
przypadku szablonów funkcji, szablon klasy definiuje nam w
rzeczywistości całą rodzinę klas. Szablon klasy !Stack! możemy zapisać
rzeczywistości całą rodzinę klas. Szablon klasy <tt>Stack</tt> możemy zapisać
następująco:
następująco:


<Source>
template<typename T> class Stack {
template<typename T> class Stack {
public:
public:
static const size_t N<nowiki>=</nowiki>100;
  static const size_t N=100;
private:
private:
 
  T _rep[N];
T rep[N];
  size_t _top;<br>
size_t top;
 
public:
public:
  Stack():_top(0) {};
  void push(T val) {_rep[_top++]=val;}
  T pop() {return _rep[--_top];}
  bool is_empty {return (_top==0);}
};
</Source>


Stos_int():_top(0) {};
([[media:Stack.cpp | Źródło: stack.cpp]])
 
void push(T val) {_rep[_top++]<nowiki>=</nowiki>val;}
T pop()          {return rep[--top];}
bool is_empty    {return (top<nowiki>=</nowiki><nowiki>=</nowiki>0);}
};
/*{mod01/code/stack.cpp}*/


Tak zdefiniowanego szablonu możemy używać podając jawnie jego argumenty.
Tak zdefiniowanego szablonu możemy używać podając jawnie jego argumenty.


<Source>
Stack<string> st ;
Stack<string> st ;
st.push("ania");
st.push("ania");
st.push("asia");
st.push("asia");
st.push("basia");
st.push("basia");
while(!st.is_empty() ){
while(!st.is_empty() ){
cout<<st.pop()<<endl;
  cout<<st.pop()<<endl;
}
}
/*{mod01/code/stack.cpp}*/
</Source>
 
([[media:Stack.cpp | Źródło: stack.cpp]])


Dla szablonów klas nie ma możliwości automatycznej dedukcji argumentów
Dla szablonów klas nie ma możliwości automatycznej dedukcji argumentów
szablonu ponieważ klasy nie posiadają argumentów wywołania które
szablonu, ponieważ klasy nie posiadają argumentów wywołania, które
mogłyby do tej dedukcji posłużyć. Jest natomiast możliwość podania
mogłyby do tej dedukcji posłużyć. Jest natomiast możliwość podania
argumentów domyślnych np.
argumentów domyślnych, np.
 
<Source>
template<typename T <nowiki>=</nowiki> int > Stack {
template<typename T = int> Stack {
...
  ...
}
}
/*{mod01/code/stack.cpp}*/
</Source>
([[media:Stack.cpp | Źródło: stack.cpp]])


Wtedy możemy korzystać ze stosu bez podawania argumentów szablonu i
Wtedy możemy korzystać ze stosu bez podawania argumentów szablonu i
wyrażenie
wyrażenie


Stack s;
<Source>Stack s;</Source>


będzie równoważne wyrażeniu:  
będzie równoważne wyrażeniu:  


Stack<int> s;  
<Source>Stack<int> s;</Source>


Dla domyślnych argmentów szablonów klas obowiązują te same reguły co dla
Dla domyślnych argmentów szablonów klas obowiązują te same reguły, co dla
domyślnych argumentów wywołania funkcji.  
domyślnych argumentów wywołania funkcji.  


Należy pamiętać że każda konkretyzacja szablony klasy dla  
Należy pamiętać, że każda konkretyzacja szablonu klasy dla  
różniących się zestawów argumentów jest osobną klasą:  
różniących się zestawów argumentów jest osobną klasą:  
 
<Source>
Stack<int> si;
Stack<int> si;
Stack<double> sd;
Stack<double> sd;
sd<nowiki>=</nowiki>si;/*błąd: to są obiekty różnych klas
sd=si; //błąd: to są obiekty różnych klas a nie zdefiniowano przypisania
/*{mod01/code/stack.cpp}*/
</Source>
([[media:Stack.cpp | Źródło: stack.cpp]])


Oczywiście stadardowy operator przypisania i tak byłby niepoprawny.
Okazuje się, że próba zdefiniowania operatora przypisania, który
Okazuje się jednak że próba zdefiniowania operatora przypisania który
np. przypisywałby do siebie stosy różnych typów, nie jest łatwa,
np. przypisywałby do siebie stosy różnych typów nie jest łatwa
ponieważ  dwa takie stosy nie widzą swoich reprezentacji.
ponieważ  dwa takie stosy nie widzą swoich reprezentacji.


Linia 639: Linia 613:
Zestaw możliwych parametrów szablonów klas jest taki sam jak dla
Zestaw możliwych parametrów szablonów klas jest taki sam jak dla
szablonów funkcji. Podobnie najczęściej wykorzystywane są wyrażenia
szablonów funkcji. Podobnie najczęściej wykorzystywane są wyrażenia
całkowitoliczbowe. W naszym przykładzie ze stosem możemu ich użyć do
całkowitoliczbowe. W naszym przykładzie ze stosem możemy ich użyć do
przekazania rozmiaru stosu:
przekazania rozmiaru stosu:


template<typename T <nowiki>=</nowiki> int , size_t N <nowiki>=</nowiki> 100> class Stack {
template<typename T <nowiki>=</nowiki> int , size_t N <nowiki>=</nowiki> 100> class Stack {
private:
private:
T rep[N];
  T rep[N];
size_t top;
  size_t top;
public:
public:
Stack():_top(0) {};
  Stack():_top(0) {};<br>
  void push(T val) {_rep[_top++]<nowiki>=</nowiki>val;}
  T pop()          {return rep[--top];}
  bool is_empty    {return (top<nowiki>=</nowiki><nowiki>=</nowiki>0);}
}


void push(T val) {_rep[_top++]<nowiki>=</nowiki>val;}
([[media:Stack_N.cpp | Źródło: stack_N.cpp]])
T pop()          {return rep[--top];}
bool is_empty    {return (top<nowiki>=</nowiki><nowiki>=</nowiki>0);}
}
/*{mod01/code/stack_N.cpp}*/


Podkreślam jeszcze raz że !Stack<int,100>! i !Stack<int,101>! to
Podkreślam jeszcze raz, że <tt>Stack<int,100></tt> i <tt>Stack<int,101></tt> to
dwie różne klasy.
dwie różne klasy.


===Szablony parametrów szablonu===
===Szablony parametrów szablonu===


Stos jest nie tyle strukturą danych ile sposobem dostępu do nich.
Stos jest nie tyle strukturą danych, ile sposobem dostępu do nich.
Stos realizuje regułę LIFO czyli Last In First Out. W tym sensie nie
Stos realizuje regułę LIFO czyli Last In First Out. W tym sensie nie
jest istotne w jaki sposób dane są na stosie przechowywane. Może to
jest istotne w jaki sposób dane są na stosie przechowywane. Może to
być tablica jak w powyższych przykładach, ale może to być praktycznie
być tablica, jak w powyższych przykładach, ale może to być praktycznie
dowolny inny kontener. Np. w Standardowej Bibliotece Szablonów C++
dowolny inny kontener. Np. w Standardowej Bibliotece Szablonów C++
(stos jest zaimplementowany jako
stos jest zaimplementowany jako
adapter do któregoś z istniejących już kontenerów. Ponieważ kontenery
adapter do któregoś z istniejących już kontenerów. Ponieważ kontenery
STL są szablonami, szablon adaptera mógłby wyglądać następująco:
STL są szablonami, szablon adaptera mógłby wyglądać następująco:


<Source>
template<typename T,
template<typename T,
template<typename X > class Sequence <nowiki>=</nowiki> std::deque >  
        template<typename X > class Sequence=std::deque >  
class Stack {
class Stack {
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();}
};
};
</Source>


Konkretyzując stos możemy wybrać kontener w którym będą przechowywane
Konkretyzując stos możemy wybrać kontener, w którym będą przechowywane
jego elementy:
jego elementy:


<Source>
Stack<double,std::vector> sv;
Stack<double,std::vector> sv;
</Source>


Można zamiast szablonu użyć zwykłego parametru typu:
Można zamiast szablonu użyć zwykłego parametru typu:


<Source>
template<typename T,typename C > class stos {
template<typename T,typename C > class stos {
C rep;
  C rep;
public:
  public:
...
  ...
}
}  
/* {stack_adapter.cpp}*/
</Source>
 
([[media:Stack_adapter.cpp | Źródło: stack_adapter.cpp]])


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


<Source>
stos<double,std::vector<double> > sv;
stos<double,std::vector<double> > sv;
</Source>


W  przypadku użycia szablonu jako parametru szablonu  
W  przypadku użycia szablonu jako parametru szablonu  
zapewniemy konsystencję pomiędzy typem !T! i kontenerem !C!,
zapewniamy konsystencję pomiędzy typem <tt>T</tt> i kontenerem <tt>C</tt>,
uniemożliwiajać pomyłkę podstawienia
uniemożliwiając pomyłkę podstawienia
niepasujących parametrów:
niepasujących parametrów:


stos<double,std::vector<int> > sv;/*błąd: niezgodność typow*/
<Source>
 
stos<double,std::vector<int> > sv; <i>błąd: niezgodność typow</i>
Uczciwość nakazuje jednak w tym miejscu stwierdzić że właśnie takie
</Source>
rozwiązanie jest zastosowane w STL-u. Ma ono zaletę że możemy
Uczciwość nakazuje jednak w tym miejscu stwierdzić, że właśnie takie
adaptować na stos dowolny kontener niekoniecznie będący szablonem.
rozwiązanie jest zastosowane w STL-u. Ma ono zaletę, że możemy
adaptować na stos dowolny kontener, niekoniecznie będący szablonem.


Na koniec jeszcze jedna uwaga: szablony kontenerów z STL posiadają po
Na koniec jeszcze jedna uwaga: szablony kontenerów z STL posiadają po
dwa parametry typów z tym że drugi posiada wartość domyślną
dwa parametry typów, z tym, że drugi posiada wartość domyślną (standard dopuszcza dowolną ilość argumentów w implemetacji kontenerów STL jak długo będą one posiadały wartości domyślne). Autorzy D. Vandervoorde, N. Josuttis <i>"C++ Szablony, Vademecum profesjonalisty"</i> ostrzegają, że w tej sytuacji kompilator może nie zaakceptować wyrażenia:
{Standard dopuszcza dowolną ilość argumentów w implemetacji
kontenerów STL jak długo będa one posiadały wartości domyślne}.
Autorzy {szablony} ostrzegają że w tej systuacji kompilator może
nie zakceptować wyrażenia:


<Source>
stos<double,std::vector> sv;
stos<double,std::vector> sv;
 
</Source>
ponieważ ignoruje fakt istnienia wartości domyślnej dla
ponieważ ignoruje fakt istnienia wartości domyślnej dla
drugiego parametru szablony !std::vector!.  
drugiego parametru szablonu <tt>std::vector</tt>.  
Mamy wtedy  niezgodność pomiędzy przekazanym argumentem szablonu
Mamy wtedy  niezgodność pomiędzy przekazanym argumentem szablonu


<Source>
template<typename T>  
template<typename T>  
std::vector<T,typename A <nowiki>=</nowiki> std::allocator<T> >;
std::vector<T,typename A = std::allocator<T> >;
 
</Source>
oraz deklaracją paremetru !Sequence! jako:
oraz deklaracją paremetru <tt>Sequence</tt> jako:


<Source>
template<typename X > class Sequence ;
template<typename X > class Sequence ;
</Source>


która zakłada tylko jeden parametr szablonu.  Można wtedy zmienić
która zakłada tylko jeden parametr szablonu.  Można wtedy zmienić
deklarację szablonu stos i podać domyślny argument dla szablony w
deklarację szablonu <tt>stos</tt> i podać domyślny argument dla szablony w
liscie parametrów:
liście parametrów:


template<typename T,template<typenamszablonye X ,typename A <nowiki>=</nowiki>
<Source>
template<typename T,template<typename X ,typename A =
std::allocator<X> > class C > class stos {...}
std::allocator<X> > class C > class stos {...}
</Source>


W praktyce używane przeze mnie kompilatory (g++ wersja ><nowiki>=</nowiki> 3.3) nie
W praktyce używane przeze mnie kompilatory (g++ wersja ><nowiki>=</nowiki> 3.3) nie
wymagały takiej konstrukcji. Przyznaję że ne udało mi się doczytać czy
wymagały takiej konstrukcji. Przyznaję, że nie udało mi się doczytać czy
jest to cecha kompilatora g++ czy nowego standardu C++(autorzy
jest to cecha kompilatora g++, czy nowego standardu C++ (autorzy D. Vandervoorde, N. Josuttis <i>"C++ Szablony, Vademecum profesjonalisty"</i> opierali się na poprzednim wydaniu standardu).
{szablony} opierali się na poprzednim wydaniu standardu).


===Konkretyzacja na żądanie===
===Konkretyzacja na żądanie===


Jak już wspomniałem wcześniej konkretyzacja szablonów może odbywać się
Jak już wspomniałem wcześniej, konkretyzacja szablonów może odbywać się
"na żądanie". W takim przypadku kompilator będzie konkretyzował
"na żądanie". W takim przypadku kompilator będzie konkretyzował
tylko funkcje napotkane w kodzie. I tak jeśli np. nie użyjemy w naszym
tylko funkcje napotkane w kodzie. I tak, jeśli np. nie użyjemy w naszym
kodzie funckji !Stack<int>::pop()! to nie zostanie ona
kodzie funckji <tt>Stack<int>::pop()</tt>, to nie zostanie ona
wygenerowana. Można z tego skorzystać i konkretyzować klasy typami
wygenerowana. Można z tego skorzystać i konkretyzować klasy typami,
które nie spełniają wszystkich ograniczeń nałożonych na parametry
które nie spełniają wszystkich ograniczeń nałożonych na parametry
szablonu. Wszystko bedzię w porządku jak długo nie będziemy używać
szablonu. Wszystko bedzię w porządku jak długo nie będziemy używać
funkcji łamiących te ograniczenia. Np. załóżmy że do szablonu
funkcji łamiących te ograniczenia. Np. załóżmy, że do szablonu
!Stack! dodajemy możliwość jego sortowania{ Wiem to nie
<tt>Stack</tt> dodajemy możliwość jego sortowania (wiem, to nie jest zgodne z duchem programowania obiektowego, stos nie posiada operacji sortowania, puryści mogą zastąpić ten przykład kontenerem <tt>list</tt>):
jest zgodne z duchem programowania obiektowego, stos nie posiada
operacji sortowania, puryści zastąpić ten przykład kontenerem
!list!. }:


template<typename T,int N> void Stack<T,N>::sort() {
template<typename T,int N> void Stack<T,N>::sort() {
bubble_sort(_rep,N);
bubble_sort(_rep,N);
};
};


Możemy teraz np. używać
Możemy teraz np. używać


Stack<std::complex<double> > sc;
Stack<std::complex<double> > sc;
sc.push( std::complex<double>(0,1));
sc.push( std::complex<double>(0,1));
sc.pop();
sc.pop();


ale nie  
ale nie  


sc.sort();
sc.sort();
/*{stack_sort.cpp}*/
 
([[media:Stack_sort.cpp | Źródło: stack_sort.cpp]])


Natomiast konkretyzacja jawna
Natomiast konkretyzacja jawna


template Stack<std::complex<double> >;
template Stack<std::complex<double> >;
/*{stack_sort.cpp}*/
 
([[media:Stack_sort.cpp | Źródło: stack_sort.cpp]])


nie powiedzie się, bo kompilator będzie się starał skonkretyzować
nie powiedzie się, bo kompilator będzie się starał skonkretyzować
wszystkie składowe klasy !Stack! w tym metodę !sort()!.  
wszystkie składowe klasy <tt>Stack</tt>, w tym metodę <tt>sort()</tt>.


===Typy stowarzyszone===
===Typy stowarzyszone===
Linia 784: Linia 768:
przydatne w przypadku szablonów. Rozważmy następujący przykład:
przydatne w przypadku szablonów. Rozważmy następujący przykład:


template<typename T> Stack {
template<typename T> Stack {
public:
public:
typedef T value_type;
typedef T value_type;
...
...
}
}


Możemy teraz używać tej definicji w innych szablonach
Możemy teraz używać tej definicji w innych szablonach


template<typename S> void f(S s) {
template<typename S> void f(S s) {
typename S::value_type total;  
  typename S::value_type total;  
/*{12cm}{słowo <tt>typename</tt> jest wymagane, inaczej  
    <i>słowo <tt>typename</tt> jest wymagane, inaczej kompilator założy, że  
kompilator założy że <tt>S::valuetype</tt> odnosi się do statycznej  
    <tt>S::value_type</tt> odnosi się do statycznej składowej klasy</i>
składowej klasy} */
  while(!s.is_empty() ) {
while(!s.is_empty() ) {
    total+<nowiki>=</nowiki>s.pop();
total+<nowiki>=</nowiki>s.pop();
  }
}
return total;
return total;
}
}
 
/*{stack_N.cpp}*/
([[media:Stack_N.cpp | Źródło: stack_N.cpp]])


Bez takich możliwości musielibyśmy przekazać typ elementów stosu w
Bez takich możliwości musielibyśmy przekazać typ elementów stosu w
osobnym argumencie. Mechanizm typów stowarzyszonych jest  
osobnym argumencie. Mechanizm typów stowarzyszonych jest  
bardzo czesto używany w uogólnionym kodzie.
bardzo czesto używany w uogólnionym kodzie.
===Podsumowanie===

Aktualna wersja na dzień 12:10, 19 paź 2021


Szablony funkcji

Funkcje uogólnione

W praktyce programowania często spotykamy się z funkcjami (algorytmami), które można zastosować do szerokiej klasy typów i struktur danych. Typowym przykładem jest funkcja obliczająca maksimum dwu wartości. Ten trywialny, aczkolwiek przydatny kod można zapisać np. w postaci:

int max(int a,int b) {
  return (a>b)?a:b;
}; 

Przykład 1.1

Funkcja max wybiera większy z dwu int-ów, ale widać, że kod będzie identyczny dla argumentów dowolnego innego typu pod warunkiem, iż istnieje dla niego operator porównania i konstruktor kopiujący. W językach programowania z silną kontrolą typów, takich jak C, C++ czy Java definiując funkcję musimy jednak podać typy przekazywanych parametrów oraz typ wartości zwracanej. Oznacza to, że dla każdego typu argumentów musimy definiować nową funkcję max:

int    max(int a, int b)      {return (a>b)?a:b;};
double max(double a,double b) {return (a>b)?a:b;};
string max(string a,string b) {return (a>b)?a:b;};
skorzystaliśmy tu z dostępnej w C++ możliwości przeładowywania funkcji
main() { cout<< max(7,5)<<end; cout<< max(3.1415,2.71)<<endl; cout<< max("Ania","Basia")<<endl; }

Przykład 1.2

Takie powtarzanie kodu, poza oczywistym zwiększeniem nakładu pracy, ma inne niepożądane efekty, związane z trudnością zapewnienia synchronizacji kodu każdej z funkcji. Jeśli np. zauważymy błąd w kodzie, to musimy go poprawić w kilku miejscach. To samo dotyczy optymalizacji kodu. W powyższym przykładzie kod jest wyjątkowo prosty, ale taki sam problem dotyczy np. funkcji sortujących. Rozważmy prosty algorytm sortowania bąbelkowego:

inline void swap(double &a,double &b) {
double   tmp=a;a=b;b=tmp;
}
void buble_sort(double *data,int N) {
for(int i=n-1;i>0;--i)
        for(int j=0; j < i;++j) 
                if(data[j]>data[j+1])
                        swap(data[j],data[j+1]);
}

Powyższa funkcja sortuje tablicę zawierającą wartości typu double, ale widać, że znów kod będzie identyczny, jeśli zamiast double użyjemy dowolnego innego typu, którego wartości możemy porównywać za pomocą funkcji operator>() i dla którego zdefiniowany jest operator przypisania. Co więcej, kod nie zmieni się jeśli zamiast tablicy użyjemy dowolnej innej struktury danych, umożliwiającej indeksowany dostęp do swoich składowych, np. std::vector ze Standardowej Biblioteki Szablonów STL. W tym przypadku kod jest już bardziej skomplikowany i kłopoty związane z jego powielaniem będą większe. Przykłady takie można mnożyć, istnieje bowiem wiele takich funkcji czy algorytmów uogólnionych. Ich kod może być znacznie bardziej skomplikowany niż w podanych przykładach, a zależność od typu argumentów nie musi ograniczać się do sygnatury, ale występować również we wnętrzu funkcji, jak np. w przypadku zmiennej tmp w funkcji swap. Powielanie takiego kodu dla różnych typów parametrów może łatwo prowadzić do błędów, utrudnia ich wykrywanie, a konieczność edycji każdego egzemplarza kodu zniechęca do wprowadzania ulepszeń.

Funkcje uogólnione bez szablonów

Jak radzili, a właściwie jak radzą sobie programiści bez możliwości skorzystania z szablonów? Tradycyjne sposoby rozwiązywania tego typu problemów to między innymi makra:

#define max(a,b) ( (a>b)?a:b) )

lub używanie wskaźników typów ogólnych, takich jak void *, jak np. w funkcji qsort ze standardowej biblioteki C:

void qsort(void *base, size_t nmemb, size_t size,
           int(*compar)(const void *, const void *));

Mimo iż użyteczne, żadne z tych rozwiązań nie może zostać uznane za wystarczająco ogólne i bezpieczne.

Można się również pokusić o próbę rozwiązania tego problemu za pomocą mechanizmów programowania obiektowego. W sumie jest to bardziej wyrafinowana odmiana rzutowania na void *. Polega na zdefiniowaniu ogólnego typu dla obiektów, które mogą być porównywane:

class GreaterThanComparable { 
public:
  virtual bool operator>(const GreaterThanComparable &) const = 0; 
};

następnie zdefiniowaniu funkcji max w postaci:

const GreaterThanComparable &
max(const GreaterThanComparable &a, 
    const GreaterThanComparable &b) { 
      return (a>b)? a:b; 
    }

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

i używaniu jej np. w następujący sposób:

class Int:public GreaterThanComparable { 
  int val;
public: Int(int i <nowiki>=</nowiki> 0):val(i) {}; 
  operator int() {return val;};
  virtual bool 
  operator>(const GreaterThanComparable &b) const {
    return val > static_cast<const Int&>(b).val;
  } 
};

main() {
  Int a(1),b(2);
  Int c;
  c = (const Int &)::max(a,b);
  cout<<(int)c<<endl;
}

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

Widać więc wyraźnie, że to podejście wymaga sporego nakładu pracy, a więc w szczególności w przypadku tak prostej funkcji jak max jest wysoce niepraktyczne. Ogólnie rzecz biorąc ma ono następujące wady:

  1. Wymaga dziedziczenia z abstrakcyjnej klasy bazowej GreaterThanComparable, czyli może być zastosowane tylko do typów zdefiniowanych przez nas. Inne typy, w tym typy wbudowane, wymagają kopertowania w klasie opakowującej, takiej jak klasa Int w powyższym przykładzie.
  2. Ponieważ potrzebujemy polimorfizmu funkcja operator>() musi być funkcją wirtualną, a więc musi być funkcją składową klasy i nie może być typu inline. W przypadku tak prostych funkcji niemożność rozwinięcia ich w miejscu wywołania może prowadzić do dużych narzutów w czasie wykonania.
  3. Funkcja max zwraca zawsze referencje do GreaterThanComparable, więc konieczne jest rzutowanie na typ wynikowy (tu Int).

Szablony funkcji

Widać, że podejście obiektowe nie nadaje się najlepiej do rozwiązywania tego szczególnego problemu powielania kodu. Dlatego w C++ wprowadzono nowy mechanizm: szablony. Szablony zezwalają na definiowanie całych rodzin funkcji, które następnie mogą być używane dla różnych typów argumentów.

Definicja szablonu funkcji max, odpowiadającej definicji 1.1 wygląda następująco:

template<typename T> T max(T a,T b) {return (a>b)?a:b;};

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

Przyjrzyjmy się jej z bliska. Wyrażenie template<typename T> oznacza, że mamy do czynienia z szablonem, który posiada jeden parametr formalny nazwany T. Słowo kluczowe typename oznacza, że parametr ten jest typem (nazwą typu). Zamiast słowa typename możmy użyć słowa kluczowego class. Nazwa tego parametru może być następnie wykorzystywana w definicji funkcji w miejscach, gdzie spodziewamy się nazwy typu. I tak powyższe wyrażenie definiuje funkcję max, która przyjmuje dwa argumenty typu T i zwraca wartość typu T, będącą wartością większego z dwu argumentów. Typ T jest na razie niewyspecyfikowany. W tym sensie szablon definiuje całą rodzinę funkcji. Konkretną funkcję z tej rodziny wybieramy poprzez podstawienie za formalny parametr T konkretnego typu będącego argumentem szablonu. Takie podstawienie nazywamy konkretyzacją szablonu. Argument szablonu umieszczamy w nawiasach ostrych za nazwą szablonu (w praktyce można uniknąć konieczności jawnej specyfikacji argumentów szablonu, opiszemy to w następnych częściach wykładu):

int i,j,k;
k=max<int>(i,j);

Takie użycie szablonu spowoduje wygenerowanie identycznej funkcji jak 1.1. W powyższym przypadku za T podstawiamy int. Oczywiście możemy podstawić za T dowolny typ i używając szablonów program 1.2 można zapisać następująco:

template<typename T> T max(T a,T b) {return (a>b)?a:b;}
main() {
  cout<<::max<int>(7,5)<<endl;
  cout<<::max<double>(3.1415,2.71)<<endl;
  cout<<::max<string>("Ania","Basia")<<endl;
}

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

W powyższym kodzie użyliśmy konstrukcji ::max(a,b). Dwa dwukropki oznaczają, że używamy funkcji max zdefiniowanej w ogólnej przestrzeni nazw. Jest to konieczne aby kod się skompilował, ponieważ szablon max istnieje już w standardowej przestrzeni nazw std. W dalszej części wykładu będziemy te podwójne dwukropki pomijać.

Oczywiście istnieją typy których podstawienie spowoduje błędy kompilacji, np.

complex<double> c1,c2;
max<complex<double> >(c1,c2); //brak operatora >

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

lub

class X {
private:
  X(const X &){};
};
X a,b;
max<X>(a,b); //prywatny (niewidoczny) konstruktor kopiujący

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

Ogólnie rzecz biorąc, każdy szablon definiuje pewną klasę typów, które mogą zostać podstawione jako jego argumenty.

Dedukcja argumentów szablonu

Użyteczność szablonów funkcji zwiększa istotnie fakt, że argumenty szablonu nie muszą być podawane jawnie. Kompilator może je wydedukować z argumentów funkcji. Tak więc zamiast

int i,j,k;
k=max<int>(i,j);

możemy napisać

int i,j,k;
k=max(i,j);

i kompilator zauważy, że tylko podstawienie int-a za T umożliwi dopasowanie sygnatury funkcji do parametrów jej wywołania i automatycznie dokona odpowiedniej konkretyzacji.

Może się zdarzyć, że podamy takie argumenty funkcji, że dopasowanie argumentów wzorca będzie niemożliwe, otrzymamy wtedy błąd kompilacji. Trzeba pamiętać, że mechanizm automatycznego dopasowywania argumentów szablonu powoduje wyłączenie automatycznej konwersji argumentów funkcji. Podanie jawnie argumentów szablonu (w nawiasach ostrych za nazwą szablonu) jednoznacznie określa sygnaturę funkcji, a więc umożliwia automatyczną konwersję typów. Ilustruje to poniższy kod:

template<typename T> T max(T a,T b) {return (a>b)?a:b;}
main() {
  cout<<::max(3.14,2)<<endl;
  // błąd: kompilator nie jest w stanie wydedukowac argumentu szablonu, bo typy 
  // argumentów (double,int) nie pasują  do (T,T)

  cout<<::max<int>(3.14,2)<<endl;
  // podając argument jawnie wymuszamy sygnaturę int max(int,int), a co za tym 
  // idzie automatyczną konwersję argumentu 1 do int-a

  cout<<::max<double>(3.14,2)<<endl;
  // podając argument szablonu jawnie wymuszamy sygnaturę 
  // double max(double,double)
  // a co za tym idzie automatyczną konwersję argumentu 2 do double-a
 
  int i;
  cout<<::max<int *>(&i,i)<<endl; 
  //błąd: nie istnieje konwersja z typu int na int*

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

Może warto zauważyć, że automatyczna dedukcja parametrów szablonu jest możliwa tylko wtedy, jeśli parametry wywołania funkcji w jakiś sposób zależą od parametrów szablonu. Jeśli tej zależności nie ma, z przyczyn oczywistych dedukcja nie jest możliwa i trzeba parametry podawać jawnie. Wtedy istotna jest kolejność parametrów na liście. Jeżeli parametry, których nie da się wydedukować, umieścimy jako pierwsze, wystarczy, że tylko je podamy jawnie, a kompilator wydedukuje resztę. Ilustruje to poniższy kod:

template<typename T,typename U> T convert(U u) {
return (T)u;
};
template<typename U,typename T> T inv_convert(U u) {
return (T)u;
};
fukcje różnią się tylko kolejnością parametrów szablonu
main() { cout<<convert(33)<<endl; błąd: kompilator nie jest w stanie wydedukować pierwszego parametru szablonu, bo nie zależy on od parametru wywołania funkcji
cout<<convert<char>(33)<<endl; w porządku: podajemy jawnie argument T, kompilator sam dedukuje argument U z typu argumentu wywołania funkcji
cout<<inv_convert<char>('a')<<endl; błąd: podajemy jawnie argument odpowiadający parametrowi U. Kompilator nie jest w stanie wydedukować argumentu T, bo nie zależy on od argumentu wywołania funkcji
cout<<inv_convert<int,char>(33)<<endl; w porządku: podajemy jawnie oba argumenty szablonu }

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

Używanie szablonów

Z użyciem szablonów wiąże się parę zagadnień niewidocznych w prostych przykładach. W językach C i C++ zwykle rozdzielamy deklarację funkcji od jej definicji i zwyczajowo umieszczamy deklarację w plikach nagłówkowych *.h, a definicję w plikach źródłowych *.c, *.cpp itp. Pliki nagłówkowe są w czasie kompilacji włączane do plików, w których chcemy korzystać z danej funkcji, a pliki źródłowe są pojedynczo kompilowane do plików “obiektowych” *.o. Następnie pliki obiektowe są łączone w jeden plik wynikowy (zob. rysunek 1.1). W pliku korzystającym z danej funkcji nie musimy więc znać jej definicji, a tylko deklarację. Na podstawie nazwy funkcji konsolidator powiąże wywołanie funkcji z jej implementacją znajdującą się w innym pliku obiektowym. W ten sposób tylko zmiana deklaracji funkcji wymaga rekompilacji plików, w których z niej korzystamy, a zmiana definicji wymaga jedynie rekompilacji pliku, w którym dana funkcja jest zdefiniowana.

<div.thumbcaption>Rysunek 1.1. Przykład organizacji kodu C++ w przypadku

użycia zwykłych funkcji.

Taka organizacja umożliwia przestrzeganie "reguły jednej definicji" (one definition rule), wymaganej przez C++. Osobom nieobeznanym z programowaniem w C/C++ zwracam uwagę na konstrukcję

#ifndef _nazwa_pliku_
#define _nazwa_pliku_
...
#endif

uniemożliwiajacą podwójne włączenie tego pliku do jednej jednostki translacyjnej.

Podobne podejście do kompilacje szablonów się nie powiedzie (zob. rysunek 1.2). Powodem jest fakt, że w trakcie kompilacji pliku utils.cpp kompilator nie wie jeszcze, że potrzebna będzie funkcja max<int>, wobec czego nie generuje kodu żadnej funkcji, a jedynie sprawdza poprawność gramatyczną szablonu. Z kolei podczas kompilacji pliku main.cpp kompilator już wie, że ma skonkretyzować szablon dla T = int, ale nie ma dostępu do kodu szablonu.

Rysunek 1.2. Przykład błędnej organizacji kodu w przypadku użycia szablonów

Istnieją różne rozwiązania tego problemu. Najprościej chyba jest zauważyć, że opisane zachowanie jest analogiczne do zachowania podczas kompilacji funkcji rozwijanych w miejscu wywołania (inline), których definicja również musi być dostępna w czasie kompilacji. Podobnie więc jak w tym przypadku możemy zamieścić wszystkie deklaracje i definicje szablonów w pliknu nagłówkowym, włączanym do plików, w ktorych z tych szablonów korzystamy (zob. rysunek 1.3). Podobnie jak w przypadku funkcji inline reguła jednej definicji zezwala na powtarzanie definicji/deklaracji szablonów w różnych jednostkach translacyjnych, pod warunkiem, że są one identyczne. Stąd konieczność umieszczania ich w plikach nagłówkowych.

Rysunek 1.3. Przykład organizacji kodu z szablonami, wykorzystującego strategię włączania.

Ten sposób organizacji pracy z szablonami, nazywany modelem włączenia, jest najbardziej uniwersalny. Jego główną wadą jestkonieczność rekompilacji całego kodu korzystającego z szablonów przy każdej zmianie definicji szablonu. Również jeśli zmienimy coś w pliku, w którym korzystamy z szablonu, to musimy rekompilować cały kod szablonu włączony do tego pliku, nawet jeśli nie uległ on zmianie. Jeśli się uwzględni fakt, że kompilacja szablonu jest bardziej skomplikowana od kompilacji "zwykłego" kodu, to duży projekt intensywnie korzystający z szablonów może wymagać bardzo długich czasów kompilacji.

Możemy też w jakiś sposób dać znać kompilatorowi, że podczas kompilacji pliku utils.cpp powinien wygenerować kod dla funkcji max<int>. Można to zrobić dodając jawne żądanie konkretyzacji szablonu (zob. rysunek 1.4):

template<typename T> T max(T a,T b) {return (a>b)?a:b;}
template int max<int>(int ,int) ; konkretyzacja jawna

Używając konkretyzacji jawnej musimy pamiętać o dokonaniu konkretyzacji każdej używanej funkcji, tak że to podejście nie skaluje się zbyt dobrze. Ponadto w przypadku szablonów klas (omawianych w następnym module) konkretyzacja jawna pociąga za sobą konkretyzację wszystkich metod danej klasy, a konkretyzacja “na żądanie” - jedynie tych używanych w programie.

<div.thumbcaption>Rysunek 1.4. Przykład organizacji kodu z szablonami,

wykorzystującego jawną konkretyzację.

Pozatypowe parametry szablonów

Poza parametrami określającymi typ, takimi jak parametr T w dotychczasowych przykładach, szablony funkcji mogą przyjmować również parametry innego rodzaju. Obecnie mogą to być inne szablony, co omówię w następnym podrozdziale lub parametry określające nie typ, ale wartości. Jak na razie (w obecnym standardzie) te wartości nie mogą być dowolne, ale muszą mieć jeden z poniższych typów:

  1. typ całkowitoliczbowy bądź typ wyliczeniowy
  2. typ wskaźnikowy
  3. typ referencyjny.

Takie parametry określające wartość nazywamy parametrami pozatypowymi. W praktyce z parametrów pozatypowych najczęściej używa się parametrów typu całkowitoliczbowego. Np.

template<size_t N,typename T> T dot_product(T *a,T *b) {
        T total=0.0;
        for(size_t i=0;i<N;++i)
                total += a[i]*b[i] ;
return total; };

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

Po raz drugi zwracam uwagę na kolejność parametrów szablonu na liście parametrów. Dzięki temu, że niededukowalny parametr N jest na pierwszym miejscu wystarczy podać jawnie tylko jego, drugi parametr typu T zostanie sam automatycznie wydedukowany na podstawie przekazanych argumentów wywołania funkcji:

main() {
double x[3],y[3];
dot_product<3>(x,y);
}

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

Parametry pozatypowe są zresztą "ciężko dedukowalne". Właściwie jedynym sposobem na przekazania wartości stałej poprzez typ argumentu wywołania jest skorzystanie z parametrów będących szablonami klas (zob. następny podrozdział).

Używając pozatypowych parametrów szablonów musimy pamiętać, że odpowiadające im argumenty muszą być stałymi wyrażeniami czasu kompilacji. Stąd jeżeli używamy typów wskaźnikowych, muszą to być wskaźniki do obiektów łączonych zewnętrznie, a nie lokalnych. Ponieważ jednak jeszcze ani razu nie używałem pozatypowych parametrów szablonów innych niż typy całkowite, to nie będę podawał żadnych przykładów takich paremtrów na tym wykładzie.

Szablony parametrów szablonu

Jak już wspomniałem w poprzednim podrozdziale, parametrami szablonu funkcji mogą być również szablony klas (zob. następny podrozdział). Szablony parametrów szablonu umożliwiają przekazanie nazwy szablonu jako argumentu szablonu funkcji. Więcej o nich napiszę w drugiej części wykładu. Tutaj tylko pokażę jako ciekawostkę w jaki sposób można dedukować wartości pozatypowych argumentów szablonu.

template< template<int N> class  C,int K>
taka definicja oznacza, że parametr C określa szablon klasy 
posiadający jeden parametr typu int. Parametr N służy tylko 
do definicji szablonu C i nie może być użyty nigdzie indziej
void f(C<K>){
  cout<<K<<endl;
};
template<int N> struct SomeClass {};
main() { SomeClass<1> c1; SomeClass<2> c2;
f(c1); C=SomeClass K=1 f(c2); C=SomeClass K=2 }

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

Szablony metod

Jak na razie definiowaliśmy szablony zwykłych funkcji. C++ umożliwia również definiowanie szablonów metod klasy np.:

struct Max {
  template<typename T> T max(T a,T b) {return (a>b)?a:b;}
};
main() {
  Max m;
  m.max(1,2);
}

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

Szablonów metod składowych dotyczą takie same reguły jak szablonów funkcji.

Szablony klas

Typy uogólnione

Uwagi na początku poprzedniego rozdziału odnoszą się w tej samej mierze do klas, jak i do funkcji. I tutaj mamy do czynienia z kodem, który w niezmienionej postaci musimy powielać dla różnych typów. Sztandarowym przykładem takiego kodu są różnego rodzaju kontenery (pojemniki), czyli obiekty służące do przechowywania innych obiektów. Jest oczywiste, że kod kontenera jest w dużej mierze niezależny od typu obiektów w nim przechowywanych. Jako przykład weźmy sobie stos liczb całkowitych. Możliwa definicja klasy stos może wyglądać następująco, choć nie polecam jej jako wzoru do naśladowania w prawdziwych aplikacjach:

class Stack {
private:
  int rep[N];
  size_t top;
public:
  static const size_t N=100;
  Stack():_top(0) {};
  void push(int val) {_rep[_top++]=val;}
  int pop() {return rep[--top];}
  bool is_empty {return (top==0);}
}

Ewidentnie ten kod będzie identyczny dla stosu obiektów dowolnego innego typu, pod warunkiem, że typ ten posiada zdefiniowany operator=() i konstruktor kopiujący.

W celu zaimplementowania kontenerów bez pomocy szablonów możemy probować podobnych sztuczek jak te opisane w poprzednim rozdziale. W językach takich jak Java czy Smalltalk, które posiadają uniwersalną klasę Object, z której są dziedziczone wszystkie inne klasy, a nie posiadają (Java już posiada) szablonów, uniwersalne kontenery są implementowane właśnie poprzez rzutowanie na ten ogólny typ. W przypadku C++ nawet to rozwiązanie nie jest praktyczne, bo C++ nie posiada pojedynczej hierarchii klas.

Szablony klas

Rozwiązaniem są znów szablony, tym razem szablony klas. Podobnie jak w przypadku szablonów funkcji, szablon klasy definiuje nam w rzeczywistości całą rodzinę klas. Szablon klasy Stack możemy zapisać następująco:

template<typename T> class Stack {
public:
  static const size_t N=100;
private:
  T _rep[N];
  size_t _top;<br>
public:
  Stack():_top(0) {};
  void push(T val) {_rep[_top++]=val;}
  T pop() {return _rep[--_top];}
  bool is_empty {return (_top==0);} 
 };

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

Tak zdefiniowanego szablonu możemy używać podając jawnie jego argumenty.

Stack<string> st ;
st.push("ania");
st.push("asia");
st.push("basia");
while(!st.is_empty() ){
  cout<<st.pop()<<endl;
}

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

Dla szablonów klas nie ma możliwości automatycznej dedukcji argumentów szablonu, ponieważ klasy nie posiadają argumentów wywołania, które mogłyby do tej dedukcji posłużyć. Jest natomiast możliwość podania argumentów domyślnych, np.

template<typename T = int> Stack {
  ...
}

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

Wtedy możemy korzystać ze stosu bez podawania argumentów szablonu i wyrażenie

Stack s;

będzie równoważne wyrażeniu:

Stack<int> s;

Dla domyślnych argmentów szablonów klas obowiązują te same reguły, co dla domyślnych argumentów wywołania funkcji.

Należy pamiętać, że każda konkretyzacja szablonu klasy dla różniących się zestawów argumentów jest osobną klasą:

Stack<int> si;
Stack<double> sd;
sd=si; //błąd: to są obiekty różnych klas a nie zdefiniowano przypisania

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

Okazuje się, że próba zdefiniowania operatora przypisania, który np. przypisywałby do siebie stosy różnych typów, nie jest łatwa, ponieważ dwa takie stosy nie widzą swoich reprezentacji.

Pozatypowe parametry szablonów klas

Zestaw możliwych parametrów szablonów klas jest taki sam jak dla szablonów funkcji. Podobnie najczęściej wykorzystywane są wyrażenia całkowitoliczbowe. W naszym przykładzie ze stosem możemy ich użyć do przekazania rozmiaru stosu:

template<typename T = int , size_t N = 100> class Stack {
private:	
 T rep[N];
 size_t top;
public:
 Stack():_top(0) {};
void push(T val) {_rep[_top++]=val;} T pop() {return rep[--top];} bool is_empty {return (top==0);} }

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

Podkreślam jeszcze raz, że Stack<int,100> i Stack<int,101> to dwie różne klasy.

Szablony parametrów szablonu

Stos jest nie tyle strukturą danych, ile sposobem dostępu do nich. Stos realizuje regułę LIFO czyli Last In First Out. W tym sensie nie jest istotne w jaki sposób dane są na stosie przechowywane. Może to być tablica, jak w powyższych przykładach, ale może to być praktycznie dowolny inny kontener. Np. w Standardowej Bibliotece Szablonów C++ stos jest zaimplementowany jako adapter do któregoś z istniejących już kontenerów. Ponieważ kontenery STL są szablonami, szablon adaptera mógłby wyglądać następująco:

template<typename T,
         template<typename X > class Sequence=std::deque > 
class Stack {
  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();}
};

Konkretyzując stos możemy wybrać kontener, w którym będą przechowywane jego elementy:

Stack<double,std::vector> sv;

Można zamiast szablonu użyć zwykłego parametru typu:

template<typename T,typename C > class stos {
  C rep;
  public:
   ...
}

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

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

stos<double,std::vector<double> > sv;

W przypadku użycia szablonu jako parametru szablonu zapewniamy konsystencję pomiędzy typem T i kontenerem C, uniemożliwiając pomyłkę podstawienia niepasujących parametrów:

stos<double,std::vector<int> > sv; <i>błąd: niezgodność typow</i>

Uczciwość nakazuje jednak w tym miejscu stwierdzić, że właśnie takie rozwiązanie jest zastosowane w STL-u. Ma ono tę zaletę, że możemy adaptować na stos dowolny kontener, niekoniecznie będący szablonem.

Na koniec jeszcze jedna uwaga: szablony kontenerów z STL posiadają po dwa parametry typów, z tym, że drugi posiada wartość domyślną (standard dopuszcza dowolną ilość argumentów w implemetacji kontenerów STL jak długo będą one posiadały wartości domyślne). Autorzy D. Vandervoorde, N. Josuttis "C++ Szablony, Vademecum profesjonalisty" ostrzegają, że w tej sytuacji kompilator może nie zaakceptować wyrażenia:

stos<double,std::vector> sv;

ponieważ ignoruje fakt istnienia wartości domyślnej dla drugiego parametru szablonu std::vector. Mamy wtedy niezgodność pomiędzy przekazanym argumentem szablonu

template<typename T> 
std::vector<T,typename A = std::allocator<T> >;

oraz deklaracją paremetru Sequence jako:

template<typename X > class Sequence ;

która zakłada tylko jeden parametr szablonu. Można wtedy zmienić deklarację szablonu stos i podać domyślny argument dla szablony w liście parametrów:

template<typename T,template<typename X ,typename A =
std::allocator<X> > class C > class stos {...}

W praktyce używane przeze mnie kompilatory (g++ wersja >= 3.3) nie wymagały takiej konstrukcji. Przyznaję, że nie udało mi się doczytać czy jest to cecha kompilatora g++, czy nowego standardu C++ (autorzy D. Vandervoorde, N. Josuttis "C++ Szablony, Vademecum profesjonalisty" opierali się na poprzednim wydaniu standardu).

Konkretyzacja na żądanie

Jak już wspomniałem wcześniej, konkretyzacja szablonów może odbywać się "na żądanie". W takim przypadku kompilator będzie konkretyzował tylko funkcje napotkane w kodzie. I tak, jeśli np. nie użyjemy w naszym kodzie funckji Stack<int>::pop(), to nie zostanie ona wygenerowana. Można z tego skorzystać i konkretyzować klasy typami, które nie spełniają wszystkich ograniczeń nałożonych na parametry szablonu. Wszystko bedzię w porządku jak długo nie będziemy używać funkcji łamiących te ograniczenia. Np. załóżmy, że do szablonu Stack dodajemy możliwość jego sortowania (wiem, to nie jest zgodne z duchem programowania obiektowego, stos nie posiada operacji sortowania, puryści mogą zastąpić ten przykład kontenerem list):

template<typename T,int N> void Stack<T,N>::sort() {
bubble_sort(_rep,N);
};

Możemy teraz np. używać

Stack<std::complex<double> > sc;
sc.push( std::complex<double>(0,1));
sc.pop();

ale nie

sc.sort();

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

Natomiast konkretyzacja jawna

template Stack<std::complex<double> >;

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

nie powiedzie się, bo kompilator będzie się starał skonkretyzować wszystkie składowe klasy Stack, w tym metodę sort().

Typy stowarzyszone

W klasach poza metodami i polami możemy definiować również typy, które będziemy nazywali stowarzyszonymi z daną klasą. Jest to szczególnie przydatne w przypadku szablonów. Rozważmy następujący przykład:

template<typename T> Stack {
public:
typedef T value_type;
...
}

Możemy teraz używać tej definicji w innych szablonach

template<typename S> void f(S s) {
  typename S::value_type total; 
    słowo typename jest wymagane, inaczej kompilator założy, że 
    S::value_type odnosi się do statycznej składowej klasy
  while(!s.is_empty() ) {
    total+=s.pop();
  }
return total;
}

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

Bez takich możliwości musielibyśmy przekazać typ elementów stosu w osobnym argumencie. Mechanizm typów stowarzyszonych jest bardzo czesto używany w uogólnionym kodzie.