Перейти к содержимому
Теория · Шаблоны · урок 1 из 11

Шаблоны функций

Чему вы научитесь

Чему вы научитесь:

  • записывать шаблон функции и шаблон класса;
  • объяснять, как компилятор выводит тип T и когда его указывают явно;
  • давать шаблону отдельную версию для одного типа;
  • писать функцию, которая принимает любое число аргументов.
Одна формочка-звезда T вырезает одинаковые печенья из двух сортов теста, int и double

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

Шаблон в C++ работает как такая формочка. Форма — это тело функции, а тесто — тип, с которым она работает. Где картинка перестаёт работать: из одного теста формочка вырезает сколько угодно печений, а компилятор создаёт версию для типа один раз и потом берёт готовую.

Зачем это нужно? Перегрузки twice(int) и twice(double) различаются только типом, а тело у них одно: return x * 2;. Для long и float понадобились бы ещё две такие же копии. Шаблон функции записывает тело один раз, а версии для нужных типов компилятор создаёт сам:

C++
#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 вместе с телом:

C++
#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 не проходит:

C++
#include <iostream>
#include "twice.h"

int main() {
    std::cout << twice(21) << '\n';  // ✗
}
TEXT
undefined reference to `int twice<int>(int)'

Это сообщение компоновщика. В задачах курса он пишет то же самое иначе: undefined symbol. В twice.cpp нет вызова с int, поэтому версия для int там не создаётся. В main.cpp нет тела шаблона, и создать её здесь тоже нельзя. Перенесите тело в twice.h.

Главное

  • Шаблон функции записывает тело один раз, а версию для каждого типа компилятор создаёт сам.
  • Инстанцирование — создание функции по шаблону для конкретного типа, по одной версии на тип.
  • Тело шаблона пишут в заголовочном файле: без тела компилятор не создаст версию.

Подпишись, чтобы пройти курс

Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.

Войти другим способом