MySQL Java JavaScript PHP Python HTML-CSS C-sharp C++ Go

Specializarea șablonului

Când definiți un șablon de clasă, compilatorul în sine generează clase din acest șablon care utilizează anumite tipuri. Totuși, putem defini noi înșine clase similare pentru un set specific de parametri ai șablonului. Astfel de definiții de clase se numesc specializări șablon. Specializarea șablonului poate fi completă sau parțială.

Specializarea completă a șablonului

În cazul specializării complete a unui șablon, sunt specificate valorile pentru toți parametrii șablonului. Iar atunci, pentru setul respectiv de argumente (tipuri), compilatorul va folosi specializarea șablonului și nu va genera o clasă pe baza șablonului generic. De exemplu:

#include <iostream> 
   
// șablon de clasă
template <typename T>
class Person {
public:
    Person(std::string name) : name{name}
    { }
    void setId(T value) { id = value;}
    void print() const
    {
        std::cout << "Id: " << id << "\tName: " << name << std::endl;
    }
private:
    T id;
    std::string name;
};
 
// specializare completă a șablonului pentru tipul unsigned
template <>
class Person<unsigned> {
public:
    Person(std::string name) : name{name}
    { 
        id = ++count;
    }
    void print() const
    {
        std::cout << "Id: " << id << "\tName: " << name << std::endl;
    }
private:
    static inline unsigned count{};
    unsigned id;
    std::string name;
};
 
int main()
{
    // obiectele sunt create pe baza specializării complete a șablonului
    Person<unsigned> tom{"Tom"};
    tom.print();    // Id: 1    Name: Tom
 
    Person<unsigned> sam{"Sam"};
    sam.print();    // Id: 2    Name: Sam
 
    // obiectul este creat pe baza clasei generate de compilator din șablon
    Person<std::string> bob{"Bob"};    // T - std::string
    bob.setId("id1345");
    bob.print();    // Id: id1345  Name: Bob
}

Mai întâi, trebuie definit șablonul propriu-zis. În acest caz, este șablonul clasei Person, care primește un parametru. Acest parametru este utilizat în interiorul șablonului pentru a defini tipul variabilei id.

După șablonul clasei urmează specializarea șablonului:

template <>
class Person<unsigned> {
public:
    Person(std::string name) : name{name}
    { 
        id = ++count;
    }
    void print() const
    {
        std::cout << "Id: " << id << "\tName: " << name << std::endl;
    }
private:
    static inline unsigned count{};
    unsigned id;
    std::string name;
};

În acest caz, specializarea este completă, deoarece pentru toți parametrii șablonului (de fapt, singurul parametru) este specificat un tip concret — în acest caz unsigned. Astfel, după cuvântul-cheie template se află paranteze unghiulare goale. Această specializare va fi aplicată doar în cazurile în care parametrul șablonului este de tip unsigned.

Specializarea unui șablon de clasă nu este obligată să aibă aceiași membri ca șablonul original: specializarea poate modifica, adăuga sau omite membri fără restricții. În acest caz, id este de tip unsigned și este generat în constructor pe baza unei variabile statice suplimentare.

Această variabilă statică este incrementată la fiecare obiect nou creat, deci fiecare nou obiect Person<unsigned> va primi un id mai mare cu 1 decât cel precedent. În plus, funcția setId nu există în specializare, deoarece nu este necesară.

În funcția main putem folosi această specializare pentru a crea obiecte Person:

Person<unsigned> tom{"Tom"};
tom.print();    // Id: 1    Name: Tom
 
Person<unsigned> sam{"Sam"};
sam.print();    // Id: 2    Name: Sam

Deoarece pentru aceste obiecte ca parametru de șablon este specificat tipul unsigned, va fi utilizată specializarea noastră a șablonului.

Pentru toți ceilalți parametri ai șablonului, compilatorul va genera singur o definiție de clasă. De exemplu:

Person<std::string> bob{"Bob"};    // T - std::string
bob.setId("id1345");
bob.print();    // Id: id1345  Name: Bob

Aici, parametrului șablonului i se transmite tipul std::string. În consecință, variabila id va fi de tip string, iar pentru a o seta se folosește funcția setId, în care se transmite un șir de caractere.

Specializare parțială

În cazul specializării parțiale, sunt specificate valori doar pentru unii dintre parametrii șablonului. De exemplu:

#include <iostream> 
   
// șablon de clasă
template <typename T, typename K>
class Person {
public:
    Person(std::string name, K phone) : name{name}, phone{phone}
    { }
    void setId(T value) { id = value;}
    void print() const
    {
        std::cout << "Id: " << id << "\tName: " << name << "\tPhone: " << phone << std::endl;
    }
private:
    T id;
    std::string name;
    K phone;
};
 
// specializare parțială a șablonului pentru tipul unsigned
template <typename K>
class Person<unsigned, K> {
public:
    Person(std::string name, K phone) : name{name}, phone{phone}
    { 
        id = ++count;
    }
    void print() const
    {
        std::cout << "Id: " << id << "\tName: " << name << "\tPhone: " << phone << std::endl;
    }
private:
    static inline unsigned count{};
    unsigned id;
    std::string name;
    K phone;
};
 
int main()
{
    Person<std::string, std::string> bob{"Bob", "+1234567688"};    // T - std::string
    bob.setId("13");
    bob.print();    // Id: 13  Name: Bob       Phone: +1234567688
 
    Person<unsigned, std::string> tom{"Tom", "+4444444444"};
    tom.print();    // Id: 1   Name: Tom       Phone: +4444444444
 
    Person<unsigned, std::string> sam{"Sam", "+555555555"};
    sam.print();    // Id: 2   Name: Sam       Phone: +555555555
}

Aici, în exemplul dat, șablonul are doi parametri T și K:

template <typename T, typename K>
class Person {

Parametrul T definește tipul pentru variabila id, iar parametrul K pentru numărul de telefon, care este stocat în variabila phone (putem transmite numărul ca șir de caractere sau ca număr).

După definiția șablonului urmează o specializare parțială pentru tipul unsigned:

template <typename K>
class Person<unsigned, K> {
    // ..........
};

Așadar, se definește doar valoarea pentru parametrul T — tipul unsigned. Valoarea parametrului K rămâne necunoscută. În acest caz, după cuvântul template se indică doar parametrii nespecificați (în cazul dat — K), pentru care valoarea nu este cunoscută.

Parantezele unghiulare de după numele clasei (class Person<unsigned, K>) indică modul în care sunt specializați parametrii șablonului. Lista trebuie să aibă același număr de parametri ca în șablonul original nespecializat. Primul parametru este unsigned, iar cel de-al doilea este același K ca în șablon.

Astfel, dacă primul dintre parametrii șablonului este unsigned, atunci pentru crearea clasei compilatorul va folosi specializarea parțială:

Person<unsigned, std::string> tom{"Tom", "+4444444444"};
tom.print();    // Id: 1   Name: Tom       Phone: +4444444444
 
Person<unsigned, std::string> sam{"Sam", "+555555555"};
sam.print();    // Id: 2   Name: Sam       Phone: +555555555

Dacă primul parametru al șablonului este un tip diferit de unsigned, atunci compilatorul va genera complet clasa pe baza șablonului:

Person<std::string, std::string> bob{"Bob", "+1234567688"};    // T - std::string
bob.setId("13");
bob.print();    // Id: 13  Name: Bob       Phone: +1234567688