Перейти к содержимому

template

template — это примитив языка C++, который вместе с механизмом перегрузки обеспечивает статический полиморфизм.

На базовом уровне template позволяет автоматизировать копипастинг перегрузок функций.

См. базовый пример

Рассмотрим пример:

int sum(std::span<int> arr)
{
int result = 0;
for (size_t i = 0; i < arr.size(); i++)
result += arr[i];
return result;
}
// Перегружаем функцию, то есть объявляем еще одну функцию с
// другим набором параметров (типами или их количеством).
float sum(std::span<float> arr)
{
float result = 0;
for (size_t i = 0; i < arr.size(); i++)
result += arr[i];
return result;
}
int main()
{
std::array<int, 3> arrInt = { 1, 2, 3 };
// Вызывает `sum` с параметром `std::span<int>`.
// Неявно приводится к `std::span<int>`
int resultInt = sum(arrInt);
std::array<float, 3> arrFloat = { 1.0f, 2.0f, 3.0f };
// Вызывает `sum` с параметром `std::span<float>`.
float resultFloat = sum(arrFloat);
return 0;
}

template позволяет написать функцию один раз — сразу для любого типа. Следующая шаблонная функция будет работать не только для float и int, но и для любого другого типа, у которого есть конструктор по умолчанию и который перегружает оператор +=.

template<typename T>
T sum(std::span<T> arr)
{
T result{};
for (size_t i = 0; i < arr.size(); i++)
result += arr[i];
return result;
}
int main()
{
std::array<int, 3> arrInt = { 1, 2, 3 };
// К `std::span<int>` приходится приводить вручную.
std::span<int> spanInt = arrInt;
int resultInt = sum(spanInt);
// Компилятор понимает, что `T` должно быть `int`, по типу переменной,
// которая передается в функцию. Это эквивалентно:
// resultInt = sum<int>(spanInt);
std::array<float, 3> arrFloat = { 1.0f, 2.0f, 3.0f };
std::span<float> spanFloat = arrFloat;
float resultFloat = sum(spanFloat);
// эквивалентно
// resultFloat = sum<float>(spanFloat);
return 0;
}

Определения шаблонных функций ведут себя немного магически, когда они лежат в заголовочном файле. Даже если они используются из нескольких единиц трансляции, линковщик достаточно умен, чтобы автоматически убрать дублирующиеся определения, порожденные такими функциями. То есть если вы вызвали sum<int> из двух разных единиц трансляции, определение sum для параметра типа int останется (как правило) в единственном экземпляре, хотя в каждой из единиц трансляции успела создаться своя копия.

Если хотите узнать больше, почитайте про One Definition Rule.

См. пример этого.

Точно так же template можно использовать для типов (struct/class). Если в типе только поля, у этого подхода нет никаких дополнительных сложностей.

См. example_3.

И это работает, что я, честно говоря, не ожидал, — даже если методы определены прямо внутри вашего типа.

См. example_4.

Проблемы с шаблонами начинаются, когда вы хотите в заголовочном файле только объявить метод/функцию, а определить его в отдельном файле реализации. В таком случае определение вообще не создается. Придется позаботиться о том, чтобы определение все же создалось. В файле, где определен шаблон, для этого можно явно инстанцировать шаблон.

Также можно импортировать файл с шаблонным определением (f.cpp) и явно инстанцировать его в другом файле.

См. example_5.

Для типов все работает похожим образом. Методы, которые в определении шаблонного класса только объявлены, но не определены, потребуют явной инстанциации шаблонного определения, чтобы обрести тело.

См. example_6

Шаблонные методы внутри шаблонных классов

Заголовок раздела «Шаблонные методы внутри шаблонных классов»

В этой ситуации нужно просто применить конструкцию template несколько раз. Все остальное работает точно так же, как в предыдущих примерах.

Передача сущностей, отличных от типов, как параметров шаблона

Заголовок раздела «Передача сущностей, отличных от типов, как параметров шаблона»

Параметрами шаблона могут быть, например, числа. Вы уже пользовались этим с std::array<Type, Size>. Вот пример:

template<size_t N> // вместо `typename` здесь указывается тип
std::array<int, N> createArray()
{
return {};
}
int main()
{
std::array<int, 10> result = createArray<10>();
return 0;
}

Такие параметры тоже могут выводиться автоматически:

template<size_t N>
void doStuff(std::array<int, N>& arr)
{
// ...
}
int main()
{
std::array arr = { 1, 2, 3 };
doStuff(arr); // вызывается `doStuff<3>`
}