Классные шаблоны и CTAD
Создание общих структур данных с шаблонами классов. Поймите определения членов, аргументы по умолчанию и то, как CTAD C++17 позволяет опустить аргументы шаблона.
Шаблоны классов
Шаблон класса ** определяет семейство классов, параметризованных одним или несколькими типами (или нетиповыми значениями). Стандартная библиотека построена на шаблонах классов: std::vector, std::map, std::optional, std::shared_ptr. Когда вы пишете std::vector, компилятор инстанцирует весь класс с T = int, генерируя конкретный тип.
Шаблоны классов следуют тем же правилам, что и шаблоны функций: они должны быть определены в заголовках, и каждый уникальный набор аргументов шаблона создает отдельный тип. Stack и Stack являются совершенно не связанными типами — они не имеют наследственных отношений.
Генерический стек
Вот простой универсальный стек. Обратите внимание, что функции членов, определенные вне тела класса, требуют повторения списка параметров шаблона.
#include <vector>
#include <stdexcept>
#include <iostream>
template <typename T>
class Stack {
private:
std::vector<T> elements_;
public:
void push(const T& value);
void pop();
const T& top() const;
bool empty() const { return elements_.empty(); }
std::size_t size() const { return elements_.size(); }
};
// Member definitions outside the class require the template header
template <typename T>
void Stack<T>::push(const T& value) {
elements_.push_back(value);
}
template <typename T>
void Stack<T>::pop() {
if (elements_.empty()) {
throw std::out_of_range("Stack<>::pop(): empty stack");
}
elements_.pop_back();
}
template <typename T>
const T& Stack<T>::top() const {
if (elements_.empty()) {
throw std::out_of_range("Stack<>::top(): empty stack");
}
return elements_.back();
}
int main() {
Stack<int> intStack;
intStack.push(42);
intStack.push(17);
std::cout << intStack.top() << '\n'; // 17
intStack.pop();
std::cout << intStack.top() << '\n'; // 42
}Несколько параметров и дефектов
Шаблоны классов могут иметь множество параметров шаблона и аргументы шаблона по умолчанию , так же как параметры функций могут иметь значения по умолчанию. Аргументы по умолчанию решаются слева направо.
#include <vector>
#include <deque>
#include <iostream>
// Container defaults to std::deque<T> — matches std::stack's design
template <typename T, typename Container = std::deque<T>>
class Stack {
private:
Container elements_;
public:
void push(const T& value) { elements_.push_back(value); }
void pop() { elements_.pop_back(); }
const T& top() const { return elements_.back(); }
bool empty() const { return elements_.empty(); }
};
int main() {
Stack<int> s1; // uses deque<int>
Stack<int, std::vector<int>> s2; // uses vector<int>
s1.push(10);
s2.push(20);
std::cout << s1.top() << '\n'; // 10
std::cout << s2.top() << '\n'; // 20
}Дедукция аргументов шаблона класса (CTAD)
До C++17 всегда приходилось указывать аргументы шаблона при построении экземпляров шаблона класса: std::pair. CTAD (Class Template Argument Deduction), представленный в C++17, позволяет компилятору выводить аргументы шаблона класса из аргументов конструктора, так же, как и дедукция аргумента шаблона функции.
С CTAD: std::pair p(1, 3.14); — компилятор выводит std::pair. Это работает для std::vector, std::tuple, std::optional, std::array (C++20) и ваших собственных шаблонов классов.
CTAD использует руководства по вычету — либо неявные (сгенерированные из конструкторов), либо явные (определяемые пользователем). Компилятор пробует каждый гид и выбирает лучший матч.
CTAD & Deduction Guides
Определяемые пользователем руководства по выводам сообщают компилятору, как сопоставить аргументы конструктора с аргументами шаблона. Они особенно полезны, когда тип аргумента конструктора прямо не соответствует параметру шаблона.
#include <iostream>
#include <cstddef>
template <typename T, std::size_t N>
struct Array {
T data[N];
};
// Without a deduction guide, CTAD cannot deduce T and N.
// This explicit deduction guide maps brace-init with N elements
// of the same type T to Array<T, N>.
template <typename First, typename... Rest>
Array(First, Rest...) -> Array<First, 1 + sizeof...(Rest)>;
// Standard library example: std::vector CTAD from iterators
#include <vector>
#include <list>
int main() {
// CTAD with our custom deduction guide
Array arr{1, 2, 3, 4, 5}; // deduces Array<int, 5>
std::cout << arr.data[2] << '\n'; // 3
// Standard library CTAD examples (C++17)
std::pair p(42, 3.14); // pair<int, double>
std::vector v{1, 2, 3}; // vector<int>
std::tuple t(1, 2.0, 'a'); // tuple<int, double, char>
// CTAD from iterator range
std::list<int> lst{10, 20, 30};
std::vector v2(lst.begin(), lst.end()); // vector<int>
}Шаблоны участников
Функции-члены шаблона класса сами по себе могут быть шаблонами, добавляя дополнительные параметры шаблона . Это называется member template**. Общим случаем использования является возможность назначения или строительства из связанной, но различной инстанциации.
Например, вы можете назначить Stack из Stack, но поскольку это не связанные типы, назначение копии по умолчанию не будет работать. Оператор назначения member template решает эту проблему:
template <typename T>
class Stack {
public:
template <typename U>
Stack& operator=(const Stack<U>& other);
};Внутри этого шаблона U может отличаться от T. Тело может копировать элементы из other, если U конвертируется в T. Этот шаблон широко используется в стандартной библиотеке — std::shared_ptr преобразуется в std::shared_ptr через конструктор шаблонов.
Когда вы специализируетесь на шаблоне класса, специализация - это совершенно новый класс - он не наследует никаких членов от основного шаблона. Вы должны переопределить функции и данные каждого участника. Забывание о переопределении члена в специализации приводит к ошибке компиляции при использовании этого члена. Кроме того, частичная специализация доступна только для шаблонов классов, а не для шаблонов функций. Мы подробно рассмотрим специализацию в следующей главе.
- Шаблоны классов определяют семейства классов — каждый уникальный набор аргументов создает отдельный тип.
- Функции-члены, определенные вне класса, должны повторять заголовок
templateи использоватьClassName.:: - Аргументы шаблона по умолчанию работают слева направо, как и аргументы функции по умолчанию
- CTAD (C++17) позволяет компилятору вывести аргументы шаблона класса из аргументов конструктора.
- Эксплицитная дедукция направляет шаблоны аргументов конструктора карт к шаблонным аргументам
- Шаблоны-члены позволяют выполнять операции через различные инстанциации одного и того же шаблона класса.
Тест — проверьте свои знания
(15 XP)1. дано `template <typename T, typename Container = std::deque<T>> class Stack`Что такое тип `Stack<int>`?
2. Что такое CTAD (C++17)?
3. Когда вы специализируетесь на шаблоне класса, что происходит с членами основного шаблона?