Шаблоны функций
Чему вы научитесь:
- записывать шаблон функции и шаблон класса;
- объяснять, как компилятор выводит тип
Tи когда его указывают явно; - давать шаблону отдельную версию для одного типа;
- писать функцию, которая принимает любое число аргументов.

Представьте формочку для печенья в виде звезды. Её делают один раз, а потом прижимают к любому тесту: к песочному, к шоколадному, к имбирному. Получаются отдельные печенья одной формы, и у каждого своё тесто.
Шаблон в C++ работает как такая формочка. Форма — это тело функции, а тесто — тип, с которым она работает. Где картинка перестаёт работать: из одного теста формочка вырезает сколько угодно печений, а компилятор создаёт версию для типа один раз и потом берёт готовую.
Зачем это нужно? Перегрузки twice(int) и twice(double) различаются только типом, а тело у них одно: return x * 2;. Для long и float понадобились бы ещё две такие же копии. Шаблон функции записывает тело один раз, а версии для нужных типов компилятор создаёт сам:
#include <iostream>
template <typename T>
T twice(T x) {
return x * 2;
}
int main() {
std::cout << twice(21) << ' ' << twice(1.25) << '\n'; // 42 2.5
}Параметр шаблона
Шаблон функции — заготовка, по которой компилятор строит обычные функции. Строка template <typename T> объявляет параметр шаблона T — имя, которое обозначает тип. T стоит в параметрах, в типе результата и в теле там, где у обычной функции был бы int или double.
Имя T — только соглашение: подойдёт и Value. Вместо typename можно написать class.
Инстанцирование
Для вызова twice(21) компилятор подставляет int вместо T и получает функцию int twice(int x). Это инстанцирование: по шаблону создаётся функция для конкретного типа. Вызов twice(1.25) создаёт вторую функцию — double twice(double x). Ещё один вызов с int использует уже созданную версию.
Тело шаблона компилятор проверяет для каждого типа отдельно. Для int и double выражение x * 2 допустимо. А строку std::string на 2 не умножить, поэтому вызов twice(std::string("ab")) не скомпилируется, хотя сам шаблон написан верно.
Шаблоны в заголовках
Чтобы создать версию для int, компилятору нужно тело шаблона: одного объявления мало. Поэтому шаблон обычно пишут в заголовочном файле twice.h вместе с телом:
#pragma once
template <typename T>
T twice(T x) {
return x * 2;
}Если заголовок с обычной функцией подключить в два .cpp-файла, сборка остановится на ошибке multiple definition. Шаблону это не грозит. Правило одного определения разрешает одинаковые определения шаблона в разных единицах трансляции, как и у inline-функций.
Частая ошибка
Шаблон разделили, как обычную функцию: в twice.h оставили объявление template <typename T> T twice(T x);, а тело перенесли в twice.cpp. Оба файла компилируются, но сборка clang++ main.cpp twice.cpp не проходит:
#include <iostream>
#include "twice.h"
int main() {
std::cout << twice(21) << '\n'; // ✗
}undefined reference to `int twice<int>(int)'Это сообщение компоновщика. В задачах курса он пишет то же самое иначе: undefined symbol. В twice.cpp нет вызова с int, поэтому версия для int там не создаётся. В main.cpp нет тела шаблона, и создать её здесь тоже нельзя. Перенесите тело в twice.h.
Главное
- Шаблон функции записывает тело один раз, а версию для каждого типа компилятор создаёт сам.
- Инстанцирование — создание функции по шаблону для конкретного типа, по одной версии на тип.
- Тело шаблона пишут в заголовочном файле: без тела компилятор не создаст версию.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.