Шаблоны в C++: функции и классы для новичка
Шаблоны позволяют написать одну функцию для int, double и своих типов вместо трёх копий. Разбираем на рабочих примерах, как объявить шаблон функции и класса и почему их держат в заголовке.

После этой статьи вы сможете написать функцию, которая работает с int, double и вашим собственным типом без копипасты, и класс-контейнер, который хранит любой тип. Займёт минут двадцать, если у вас уже установлен компилятор с поддержкой C++17 или новее (GCC 11+, Clang 14+, MSVC 2022). Всё, что нужно — текстовый редактор и умение собрать один файл командой g++ или через IDE.
Что понадобится до начала
- Компилятор с поддержкой хотя бы C++17. Проверить версию GCC:
g++ --version. Все примеры собираются флагом-std=c++17. - Умение компилировать один
.cpp-файл. Пример команды дан ниже, повторяется для каждого фрагмента. - Базовое понимание функций и классов в C++ — что такое параметр, возвращаемое значение, поля класса. Шаблоны надстраиваются поверх этого.
Одна оговорка сразу: код шаблонов почти всегда живёт целиком в заголовочном файле, а не разбивается на .h и .cpp. Почему — в конце. Пока пишите всё в один файл, так проще.
Шаг 1. Функция, которая работает с любым типом
Классический случай: нужна функция максимум из двух. Без шаблонов пришлось бы писать отдельную версию для каждого типа. Шаблон делает тип параметром.
// max_demo.cpp — C++17
#include <iostream>
#include <string>
// T — имя типа-параметра, подставится при вызове
template <typename T>
T maximum(T left, T right) {
return (left > right) ? left : right;
}
int main() {
std::cout << maximum(3, 7) << "\n"; // T = int
std::cout << maximum(2.5, 1.1) << "\n"; // T = double
std::cout << maximum(std::string("abc"),
std::string("abd")) << "\n"; // T = std::string
return 0;
}
Соберите и запустите:
g++ -std=c++17 max_demo.cpp -o max_demo
./max_demo
Как понять, что получилось: в консоли появятся три строки — 7, 2.5 и abd. Одна функция обслужила три разных типа. Компилятор сам вывел T из аргументов — это называется выведение аргументов шаблона.
Ключевое слово typename здесь можно заменить на class — они взаимозаменяемы в объявлении параметра-типа. Разницы в поведении нет, typename читается яснее.
Когда тип нужно указать явно
Если компилятор не может вывести тип из аргументов — укажите его в угловых скобках сами:
// принудительно T = double, даже если передали int
std::cout << maximum<double>(3, 7.0) << "\n";
Без этого вызов maximum(3, 7.0) не соберётся: первый аргумент int, второй double, и компилятор не знает, какой T взять.
Шаг 2. Шаблон класса — контейнер под любой тип
Теперь класс. Сделаем простую пару значений — как урезанный std::pair, чтобы увидеть механику. Тип-параметр объявляется так же, но используется в полях и методах.
// box_demo.cpp — C++17
#include <iostream>
#include <string>
template <typename T>
class Box {
public:
explicit Box(T value) : content_(value) {}
T get() const { return content_; }
void set(T value) { content_ = value; }
private:
T content_;
};
int main() {
Box<int> number(42);
Box<std::string> text("hello");
std::cout << number.get() << "\n"; // 42
std::cout << text.get() << "\n"; // hello
return 0;
}
Собираем той же командой, подставив имя файла. Как понять, что получилось: выведутся 42 и hello.
Разница с функцией важная: для класса тип в угловых скобках указывают всегда — Box<int>, Box<std::string>. Компилятор не выводит его из аргумента конструктора автоматически до C++17 без специальных механизмов, а в учебных целях привыкайте указывать явно.
Метод с собственным шаблонным параметром
Метод класса может принимать ещё один тип, не связанный с типом класса:
template <typename T>
class Box {
public:
explicit Box(T value) : content_(value) {}
// U — отдельный тип, только для этого метода
template <typename U>
bool sameAs(const Box<U>& other) const {
return get() == other.get();
}
T get() const { return content_; }
private:
T content_;
};
Сравнение Box<int> и Box<double> тут скомпилируется, потому что int и double сравнимы через ==. С несравнимыми типами компилятор выдаст ошибку — и это подводит к главной особенности шаблонов.
Как это работает под капотом — коротко
Шаблон сам по себе не порождает машинный код. Это заготовка. Настоящую функцию или класс компилятор создаёт в момент использования — подставляет конкретный тип и генерирует отдельную версию. Процесс называется инстанцированием.
Вы пишете одну функцию, ноmaximum(3, 7)иmaximum(2.5, 1.1)— это две разные функции в итоговом бинарнике. Компилятор сгенерировал каждую отдельно. Отсюда и требования, и ограничения шаблонов.
Практическое следствие: ошибку в шаблоне вы часто увидите не там, где написали шаблон, а там, где его использовали с неподходящим типом. Отсюда длинные и пугающие сообщения компилятора.
Значения-параметры и несколько типов сразу
Параметром шаблона может быть не только тип, но и целочисленная константа, а типов может быть несколько:
// массив фиксированного размера N элементов типа T
template <typename T, int N>
class FixedArray {
public:
T& at(int index) { return data_[index]; }
int size() const { return N; }
private:
T data_[N];
};
// использование:
FixedArray<double, 5> buffer; // 5 элементов double
Здесь N — параметр-значение, известное на этапе компиляции. Размер buffer зафиксирован в типе. Именно так устроен стандартный std::array<T, N>.
Если не получилось
no matching function for call to 'maximum(int, double)'— типы аргументов разные, компилятор не смог вывести одинT. Приведите аргументы к одному типу или укажите тип явно:maximum<double>(3, 7.0).use of class template 'Box' requires template arguments— вы написалиBox b(42)вместоBox<int> b(42). Для шаблонного класса тип в скобках нужен явно.undefined reference to ...при сборке нескольких файлов — вы вынесли реализацию шаблона в.cpp. Так делать нельзя (см. ниже). Держите определение шаблона в заголовке.no match for 'operator>'внутриmaximum— вы вызвали шаблон с типом, у которого нет операции сравнения. Либо перегрузитеoperator>для своего типа, либо не используйте функцию с ним.- Гора текста на 40 строк вместо ошибки — типично для шаблонов. Ищите в сообщении фразу
required from here: она указывает строку с реальным вызовом, где всё пошло не так.
Почему шаблоны держат в заголовке
Компилятор генерирует код шаблона только когда видит и объявление, и определение, и конкретный тип использования — одновременно, в одной единице трансляции. Если реализацию maximum положить в отдельный .cpp, другой файл увидит только объявление и не сможет инстанцировать функцию для своего типа. На линковке получите undefined reference.
Поэтому шаблонные функции и классы почти всегда пишут полностью в .h или .hpp. Это не стилевое предпочтение, а следствие модели компиляции.
Явное инстанцирование в .cpp для заранее известного набора типов существует, но новичку оно не нужно — это отдельная тема, здесь её не разворачиваем.
Что делать дальше
Вы умеете писать шаблон функции и класса и понимаете, откуда берутся странные ошибки компилятора. Следующие шаги:
- Посмотрите, как устроены
std::vectorиstd::pair— это шаблоны классов, с которыми вы уже работали, не зная этого. - Изучите вариативные шаблоны (
template <typename... Args>) — когда число параметров заранее неизвестно. - Если у вас C++20 — посмотрите
concepts: они ограничивают, какие типы допустимы для шаблона, и делают ошибки читаемыми вместо стены текста.
Создатель презентаций: Структура и текст каждого слайда: заголовки-утверждения, одна мысль на слайд, заметки докладчику.
Спросить за 150 ₽Источники: cppreference — Templates, cppreference — Class template
Частые вопросы
У меня старый компилятор с C++11, примеры соберутся?
В чём разница между typename и class в объявлении шаблона?
Почему нельзя вынести реализацию шаблона в отдельный .cpp?
Компилятор выдаёт стену ошибок на 40 строк, что с этим делать?
Обязательно ли указывать тип в угловых скобках при вызове?
Может ли параметром шаблона быть не тип, а число?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.