template
template — это примитив языка C++, который
вместе с механизмом перегрузки обеспечивает статический полиморфизм.
Базовое использование template
Заголовок раздела «Базовое использование template»На базовом уровне 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 для типов
Заголовок раздела «template для типов»Точно так же 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>`}