Шаблоны — это функция языка программирования C++ , которая позволяет функциям и классам работать с универсальными типами . Это позволяет объявлению функции или класса ссылаться через универсальную переменную на другой другой класс (встроенный или вновь объявленный тип данных ) без создания полного объявления для каждого из этих различных классов.
Проще говоря, шаблонный класс или функция были бы эквивалентны (до «компиляции») копированию и вставке шаблонного блока кода, где он используется, и последующей замене параметра шаблона фактическим. По этой причине классы, использующие шаблонные методы, помещают реализацию в заголовки (файлы *.h), поскольку ни один символ не может быть скомпилирован без предварительного знания типа.
Стандартная библиотека C++ предоставляет множество полезных функций в рамках связанных шаблонов.
Основными источниками вдохновения для шаблонов C++ были параметризованные модули, предоставляемые языком CLU , и обобщенные типы, предоставляемые Ada . [1]
Существует три вида шаблонов: шаблоны функций , шаблоны классов и, начиная с C++14 , шаблоны переменных . Начиная с C++11 , шаблоны могут быть как вариативными , так и невариативными; в более ранних версиях C++ они всегда невариативны.
Шаблон функции ведет себя как функция, за исключением того, что шаблон может иметь аргументы многих различных типов (см. пример). Другими словами, шаблон функции представляет собой семейство функций. Формат объявления шаблонов функций с параметрами типа:
объявление шаблона < идентификатор класса > ; объявление шаблона < идентификатор имени типа > ;
Оба выражения имеют одинаковое значение и ведут себя совершенно одинаково. Последняя форма была введена, чтобы избежать путаницы, [2] поскольку параметр типа не обязательно должен быть классом до C++20. (Это может быть базовый тип, такой как int
или double
.)
Например, стандартная библиотека C++ содержит шаблон функции max(x, y)
, который возвращает большее из x
и y
. Этот шаблон функции можно определить следующим образом:
шаблон < имя_типа T > const T & max ( const T & a , const T & b ) { return a < b ? b : a ; }
Это определение одной функции работает со многими типами данных. В частности, оно работает со всеми типами данных, для которых определен < (оператор «меньше»), и возвращает значение с типом, преобразуемым в bool
. Использование шаблона функции экономит место в файле исходного кода, а также ограничивает изменения одним описанием функции и делает код более удобным для чтения.
Однако шаблон инстанцированной функции обычно создает тот же объектный код, что и при написании отдельных функций для всех различных типов данных, используемых в конкретной программе. Например, если программа использует как int
и double
версию max()
шаблона функции выше, компилятор создаст версию объектного кода , max()
которая работает с int
аргументами, и другую версию объектного кода, которая работает с double
аргументами. [ необходима цитата ] Вывод компилятора будет идентичен тому, что было бы создано, если бы исходный код содержал две отдельные нешаблонные версии max()
, одну, написанную для обработки int
, и одну, написанную для обработки double
.
Вот как можно использовать шаблон функции:
#include <iostream> int main () { // Это вызовет max<int> путем неявного вывода аргумента. std :: cout << std :: max ( 3 , 7 ) << '\n' ; // Это вызовет max<double> путем неявного вывода аргумента. std :: cout << std :: max ( 3.0 , 7.0 ) << '\n' ; // Нам нужно явно указать тип аргументов; // хотя std::type_identity мог бы решить эту проблему... std :: cout << max < double > ( 3 , 7.0 ) << '\n' ; }
В первых двух случаях аргумент шаблона T
автоматически выводится компилятором как int
и double
, соответственно. В третьем случае автоматический вывод max(3, 7.0)
не сработает, поскольку тип параметров должен в общем случае точно соответствовать аргументам шаблона. Поэтому мы явно создаем версию double
с max<double>()
.
Этот шаблон функции может быть инстанцирован любым копируемым конструируемым типом, для которого выражение y < x
является допустимым. Для определяемых пользователем типов это подразумевает, что оператор "меньше" ( <
) должен быть перегружен в типе.
Начиная с C++20 , при использовании auto
или Concept auto
в любом из параметров объявления функции это объявление становится сокращенным объявлением шаблона функции . [3] Такое объявление объявляет шаблон функции, и один изобретенный параметр шаблона для каждого заполнителя добавляется к списку параметров шаблона:
void f1 ( auto ); // то же, что и template<class T> void f1(T) void f2 ( C1 auto ); // то же, что и template<C1 T> void f2(T), если C1 является концепцией void f3 ( C2 auto ...); // то же, что и template<C2... Ts> void f3(Ts...), если C2 является концепцией void f4 ( C2 auto , ...); // то же, что и template<C2 T> void f4(T...), если C2 является концепцией void f5 ( const C3 auto * , C4 auto & ); // то же, что и template<C3 T, C4 U> void f5(const T*, U&);
Шаблон класса предоставляет спецификацию для генерации классов на основе параметров. Шаблоны классов обычно используются для реализации контейнеров . Шаблон класса создается путем передачи ему заданного набора типов в качестве аргументов шаблона. [4] Стандартная библиотека C++ содержит множество шаблонов классов, в частности контейнеры, адаптированные из Стандартной библиотеки шаблонов , такие как vector
.
В C++14 шаблоны также можно использовать для переменных, как в следующем примере:
template < typename T > constexpr T pi = T { 3.141592653589793238462643383L }; // (Почти) из std::numbers::pi
Хотя шаблонизация типов, как в примерах выше, является наиболее распространенной формой шаблонизации в C++, также возможно шаблонизировать значения. Так, например, класс, объявленный с помощью
шаблон < int K > класс MyClass ;
может быть создан с помощью определенного int
.
В качестве примера из реальной жизни можно привести стандартный библиотечный тип массива фиксированного размера std::array
, шаблон которого основан как на типе (представляющем тип объекта, который хранится в массиве), так и на числе, имеющем тип std::size_t
(представляющем количество элементов, которые хранится в массиве). Его std::array
можно объявить следующим образом:
шаблон < класс T , размер_t N > структура массива ;
и массив из шести char
s может быть объявлен:
массив < символ , 6 > мойМассив ;
Когда функция или класс создается из шаблона, компилятор создает специализацию этого шаблона для набора используемых аргументов, и эта специализация называется сгенерированной специализацией.
Иногда программист может решить реализовать специальную версию функции (или класса) для заданного набора аргументов типа шаблона, что называется явной специализацией. Таким образом, определенные типы шаблонов могут иметь специализированную реализацию, оптимизированную для типа, или более осмысленную реализацию, чем общая реализация.
Явная специализация используется, когда поведение функции или класса для конкретных выборов параметров шаблона должно отклоняться от общего поведения: то есть от кода, сгенерированного основным шаблоном или шаблонами. Например, определение шаблона ниже определяет конкретную реализацию для max()
аргументов типа const char*
:
#include <cstring> template <> const char * max ( const char * a , const char * b ) { // Обычно результатом прямого сравнения // двух строк C является неопределенное поведение; // использование std::strcmp делает определенным. return std :: strcmp ( a , b ) > 0 ? a : b ; }
В C++11 появились вариативные шаблоны , которые могут принимать переменное число аргументов способом, несколько похожим на вариативные функции, такие как std::printf
.
В C++11 появились псевдонимы шаблонов, которые действуют как параметризованные определения типов .
Следующий код показывает определение шаблонного псевдонима StrMap
. Это позволяет, например, StrMap<int>
использовать его в качестве сокращения для std::unordered_map<int,std::string>
.
шаблон < typename T > с использованием StrMap = std :: unordered_map < T , std :: string > ;
Изначально концепция шаблонов не была включена в некоторые языки, такие как Java и C# 1.0. Принятие Java обобщений имитирует поведение шаблонов, но технически отличается. C# добавил обобщения (параметризованные типы) в .NET 2.0. Обобщения в Ada появились раньше шаблонов C++.
Хотя шаблоны C++, обобщенные типы Java и обобщенные типы .NET часто считаются схожими, обобщенные типы лишь имитируют базовое поведение шаблонов C++ . [5] Некоторые из расширенных функций шаблонов, используемых такими библиотеками, как Boost и STLSoft, и реализациями STL для метапрограммирования шаблонов (явная или частичная специализация, аргументы шаблонов по умолчанию, аргументы шаблонов, не являющиеся типами, аргументы шаблонов шаблонов, ...) недоступны для обобщенных типов.
В шаблонах C++ случаи компиляции исторически выполнялись путем сопоставления шаблона с аргументами шаблона. Например, базовый класс шаблона в примере Factorial ниже реализован путем сопоставления 0, а не с проверкой неравенства, которая ранее была недоступна. Однако появление в C++11 функций стандартной библиотеки, таких как std::conditional, предоставило другой, более гибкий способ обработки условного создания экземпляра шаблона.
// Шаблон индукции < unsigned N > struct Factorial { static constexpr unsigned value = N * Factorial < N - 1 >:: value ; }; // Базовый случай через специализацию шаблона: template <> struct Factorial < 0 > { static constexpr unsigned value = 1 ; };
Используя эти определения, можно вычислить, скажем, 6! во время компиляции, используя выражение Factorial<6>::value
.
В качестве альтернативы constexpr
в C++11 / if constexpr
в C++17 можно использовать для вычисления таких значений напрямую с помощью функции во время компиляции:
шаблон < беззнаковый N > беззнаковый факториал () { если constexpr ( N <= 1 ) вернуть 1 ; иначе вернуть N * факториал < N -1 > (); }
По этой причине шаблонное метапрограммирование в настоящее время в основном используется для выполнения операций над типами.