Шаблон функции (function template) — заготовка функции, в которой тип оставлен параметром. Конкретную функцию компилятор создаёт сам, подставив тип, выведенный из аргументов вызова.

Зачем нужно

Swap для int, float, double отличаются только типом. Писать перегрузку на каждый тип — копирование. Шаблон описывает алгоритм один раз для любого подходящего типа.

Как работает

template <typename T>
void Swap(T& lhs, T& rhs) {
  T tmp = lhs;
  lhs = rhs;
  rhs = tmp;
}
 
double lhs = 2, rhs = 1;
Swap(lhs, rhs);  // T выведен как double

template <typename T> объявляет параметр шаблона T. При вызове компилятор смотрит на аргументы, выводит T и создаёт (инстанцирует) обычную функцию Swap(double&, double&). Так шаблон превращается в перегрузку функции.

Не на этапе препроцессинга

В коде лекции сказано, что шаблоны превращаются в перегрузки «на этапе препроцессинга». Это неверно: препроцессор шаблоны не трогает (они видны в выводе g++ -E как есть), инстанцирование делает компилятор. См. Компиляция и сборка.

Несколько параметров и явное указание

template <typename T, typename U>
void PrintValues(T x, U y) {
  std::cout << "x=" << x << ' ' << "y=" << y << '\n';
}
 
PrintValues(rx, 0.0);        // T = int, U = double
PrintValues<float>(1, 0.0);  // T = float задан явно, U выведен: double

Правила вывода

Типы выводятся для аргументов независимо. При выведении по значению (параметр T x):

  1. отбрасываются const и volatile;
  2. ссылочность аргумента теряется;
  3. массив низводится до указателя на первый элемент;
  4. функция низводится до указателя на функцию.

Поэтому для int& rx выводится T = int, для const int cx — тоже int, для int a[3] — int*.

Не из лекции

  • «Независимо» значит, что выводы должны совпасть: Swap(a, c) с float a и double c — ошибка deduced conflicting types for parameter 'T'.
  • Правила 1–4 — для параметра по значению. В Swap(T& lhs, T& rhs) ссылка сохраняет тип аргумента как есть.
  • volatile — квалификатор вроде const: объект может измениться в обход программы, поэтому компилятор не оптимизирует обращения к нему.
  • Проверено на GCC 16.2.

Тонкости

  • Если подходят и обычная функция, и шаблон, при равных преобразованиях выбирается обычная функция. В коде лекции Swap(z, v) с float вызывает нешаблонную Swap(float&, float&), а для double работает шаблон.
  • T в теле — полноценный тип: T tmp = lhs;.

Частые ошибки

  • Передать аргументы разных типов туда, где оба параметра T.
  • Ждать, что T для const int выведется как const int при передаче по значению.
  • Думать, что шаблон обрабатывает препроцессор.