C++: szablony II

Użycie nazwy szablonu klasy w ciele szablonu klasy

Gdziekolwiek w ciele szablonu klasy użyta jest nazwa szablonu klasy, domyślnie jest ona uzupełniona listą parametrów aktualnych przepisaną z listy parametrów formalnych szablonu klasy.

Na przykład zapis

template <typename T>
class pojemnik{
  T zawartosc;
  ....
  pojemnik(pojemnik& p); // konstruktor kopiujący
}

jest równoważny zapisowi
template <typename T>
class pojemnik{
  T zawartosc;
  ....
  pojemnik<T>(pojemnik<T>& p); // konstruktor kopiujący
}

Domyślne argumenty szablonu

Dla szablonów klas istnieje możliwość określania domyślnych wartości parametrów szablonu w sposób podobny do tego jak zadaje się domyślne argumenty funkcji.

template<typename T=double, int CAPACITY=20>
class Stack{
   ...
}
...
Stack<> s1;
Stack<int> s2;

Szablon klasy Stack z parametrami domyślnymi

  • W poprzednich standardach C++ argumentów domyślnych nie można było stosować w szablonach funkcji (w C+11 już można).

Sparametryzowane definicje składników szablonu klasy

  • Główna różnica pomiędzy szablonem, a definicją sparametryzowaną polega na tym, że szablon może mieć dowolne dopuszczalne w szablonach typy parametrów, a definicja sparametryzowana odnosi się tylko do składników szablonu klasy i musi mieć typy parametrów dokładnie te same i w tej samej kolejności co na liście szablonu klasy.
  • Co więcej, przy każdej konkretyzacji stosowane są wartości parametrów podyktowane wartościami parametrów klasy.

Definiowane metod szablonu klasy

W tym celu postępujemy następująco
template<typename T>
void pojemnik<T>::pokaz(){
  cout << "W pojemniku jest" << zawartosc << endl;
}

Funkcje zdefiniowane w ciele szablonu klasy są traktowane jako inline

Składowe statyczne szablonu klasy

Szablon klasy Stack ze składnikami statycznymi

Klasy zagnieżdżone w szablonie klasy

Szablon klasy Lista z zewnętrzną definicją klasy zagnieżdżonej

Szablony składowe

Funkcje specjalizowane

Jednak w praktyce raczej chodzić nam będzie o porównanie tekstów, a nie o porównanie wskaźników.

inline char* const& max(char* const& a, char* const& b){
  if(strcmp(a,b)<0) return b;
  else return a;
}

  • Funkcją specjalizowaną nazywamy funkcję, która może też być wygenerowana z pewnego szablonu.
  • Jeśli kompilator ma do wyboru funkcję specjalizowaną i funkcję wygenerowaną z szablonu, wybiera funkcję specjalizowaną.

Przykład wykorzystania funkcji specjalizowanej

Jeśli kompilator nie wie o istnieniu funkcji specjalizowanej w momencie generowania kodu wywołania funkcji, skorzysta z szablonu.

Znaczenie kolejności przy korzystaniu z funkcji specjalizowanej

Szablony, a przeładowanie

Przeładowanie szablonów funkcji

Oto szablon funkcji max dla argumentu dowolnego typu i argumentu typu int.

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

A to szablon funkcji max dla trzech argumentów dowolnego typu.

template<typename T>
inline T const& max(T const& a, T const& b, T const& c){
  return max(max(a,b),c);
}

Przykład z przeładowaniem szablonów

Dopasowanie argumentów w obecności szablonów funkcji

W przypadku, gdy wśród przeładowanych funkcji pojawiają się również szablony, do ustalenia, która z przeładowanych wersji danej funkcji ma zostać wybrana, stosowane są, w podanej kolejności, następujące reguły

  1. ścisła zgodność argumentów z jedną z funkcji specjalizowanych
  2. możliwość wygenerowania funkcji o identycznej liście argumentów w oparciu o szablon
  3. dopasowanie funkcji specjalizowanej na zasadzie utożsamienia typów (np. int i const int)
  4. zgodność argumentów z jedną z funkcji specjalizowanych przy wykorzystaniu konwersji standardowych w zakresie typów całkowitych rozszerzających pojemność typu (n.p. bool na int) oraz konwersja float na double
  5. zgodność argumentów z jedną z funkcji specjalizowanych przy wykorzystaniu pozostałych standardowych konwersji (np. int na double, int* na void*)
  6. zgodność argumentów z jedną z funkcji specjalizowanych przy wykorzystaniu konwersji zdefiniowanych przez użytkownika
Jeśli okaże się, że dopasowane zostaną dwie lub więcej funkcje na najwyższym poziomie zgodności, sygnalizowany jest błąd niejednoznaczności.

C++11: Szablony ze zmienną listą parametrów (variadic templates)

  template<typename... Params>
  void printf(const std::string &strFormat, Params... parameters);

  template<typename... Values> class MyClass1;
  class MyClass1<int, double> MyInstanceA; 

  class MyClass1<> MyInstanceB; 
  template<typename First, typename... Rest> class MyClass2;

C++11: Lokalne i nienazwane typy jako parametry szablonu

void myFunc1(vector<X>& v){
    struct Cmp{
    bool operator(const X& a, const X& b){
        return a.v < b.v; }
    };

    sort(v.begin, v.end, Cmp());	// Poprawne tylko w C++11 bo Cmp lokalne
}
void myFunc1(vector<X>& v){

    sort(v.begin, v.end, 
         [] (const X& a, const X& b) { return a.v < b.v; });	// Poprawne tylko w C++11
}
template<typename T> void myFunc2(T const& t){}
    enum X {x};
    enum {y};
    
    int main()
    {
        myFunc2(x);     // Poprawne tylko w C++11
        myFunc2(y);     // Poprawne tylko w C++11
        enum Z {z};
        myFunc2(z);     // Poprawne tylko w C++11
    }

C++11: Aliasy szablonów (template aliases)

   template<typename T1, typename T2, int T3> class MyClass1;
   template<typename T2> typedef MyClass1<MyClass2, T2, 5> TypedefName;
   template<typename T1, typename T2, int T3> class MyClass1;
   template<typename T2> using TypedefName = MyClass1<MyClass2, T2, 5>

Specjalizacje szablonów klas

Szablon klasy Stack ze specjalizacją dla typu bool

Specjalizowane funkcje składowe

Specjalizacje częściowe

Stopień wyspecjalizowania

Przykład rozstrzygania stopnia specjalizacji

Przyjaźń, a szablony klas

Jawna konkretyzacja

Przykłady szablonów klas

Szablon klasy rationalNumber

Szablon klasy parametrizedNumber

Szablon klasy vector

Polimorfizm dynamiczny i statyczny

  • Zaletą polimorfizmu statycznego jest wysoka efektywność wygenerowanego kodu, która jest możliwa dzięki rozstrzyganiu decyzji o doborze formy w trakcie kompilacji
  • Wyższość polimorfizmu dynamicznego ujawnia się tam, gdzie w czasie wykonania zachodzi konieczność równoczesnego przesyłania do tej samej metody uogólnionej różnych form.