Специализация шаблона

Последнее обновление: 19.03.2023

При определении шаблона класса компилятор сам генерирует по этому шаблону классы, которые применяют определенные типы. Однако мы сами можем определить подобные классы для конкретного набора параметров шаблона. Подобные определения классов называются специализацией шаблона. Специализация шаблона может быть полной и частичной.

Полная специализация шаблона

При полной специализации шаблона указываются значения для всех параметров шаблона. И тогда для указанного набора аргументов (типов) компилятор будет использовать специализацию шаблона, а не создавать класс на основе шаблона. Например:

#include <iostream> 
  
// шаблон класса
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;
};

// полная специализация шаблона для типа 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()
{
	// объекты создаются на основе полной специализации шаблона
    Person<unsigned> tom{"Tom"};
    tom.print();    // Id: 1    Name: Tom

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

	// объект создается на основе класса, генерируемого компилятором по шаблону
    Person<std::string> bob{"Bob"};    // T - std::string
    bob.setId("id1345");
    bob.print();    // Id: id1345  Name: Bob
}

Вначале надо определить сам шаблон. В данном случае это шаблон класса Person, который принимает один параметр. Этот параметр внутри шаблона используется для определения типа переменной id.

После шаблона класса идет специализация шаблона:

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

В данном случае специализация полная, так как для всех параметров шаблона (по сути для единственного параметра шаблона) указано значение - в данном случае тип unsigned. В этом случае после ключевого слова template идут пустые угловые скобки. То есть данная специализация будет применяться только для тех случаев, когда параметр шаблона представляет тип unsigned.

Cпециализация шаблона класса необязательно должна иметь те же члены, что и сам шаблон: специализация шаблона может изменять, добавлять или опускать члены без ограничений. Так, в данном случае id представляет тип unsigned и генерируется в конструкторе на основе дополнительно добавленной статической переменной. Эта статическая переменная увеличивается с каждым новым созданным объектом, поэтому каждый новый объект Person<unsigned> для id будет получать значение на 1 больше, чем предыдущего. При этом функции setId в специализации нет, он нам не нужен.

В функции main мы можем использовать эту специализацию для создания объектов Person:

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

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

Поскольку для этих объектов в качестве параметра шаблона указан тип unsigned, то будет использоваться наша специализация шаблона.

Для всех других параметров шаблона компилятор будет сам создавать определение класса. Например:

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

Здесь параметру шаблона передается в качестве значения тип std::string. Соответственно переменная id будет представлять строку, а для ее установки применяется функция setId, в которую передается строка.

Частичная специализация

При частичной специализации указываются значения не для всех параметров шаблона. Например:

#include <iostream> 
  
// шаблон класса
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;
};

// частичная специализация шаблона для типа 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
}

Здесь для примера шаблон имеет два параметра T и K:

template <typename T, typename K>
class Person {

Параметр T устанавливает тип для переменной id, а параметр K - для номера телефона, который хранится в переменной phone (мы можем передать номер телефона в виде строки или числа - последовательности цифр).

После определения шаблона идет частичная специализация шаблона для типа unsigned:

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

То есть определяется только значение для параметра T - это тип unsigned. Значение параметра K по прежнему остается неизвестным. И в этом случае после ключевого слова template указываем неустановленные параметра (в данном случае параметр K), для которых значение неизвестно.

Угловые скобки после названия класса (class Person<unsigned, K>) указывают как специализируются параметры шаблона. Список здесь должен иметь то же количество параметров, что и в исходном неспециализированном шаблоне. Первый параметр для этой специализации — unsigned. Другой параметр указывается как соответствующее имя параметра в оригинальном шаблоне.

Таким образом, если первым среди параметров шаблонов указывается unsigned, то для создания класса компилятор использует частичную специализацию:

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

Если первым значением для параметров шаблона указан тип, отличный от unsigned, тогда компилятор полностью сам генерирует определение класса:

Person<std::string, std::string> bob{"Bob", "+1234567688"};    // T - std::string
bob.setId("13");
bob.print();    // Id: 13  Name: Bob       Phone: +1234567688
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850