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

Template c что это

  • автор:

#24 – Шаблоны функций (template)

#24 – Шаблоны функций (template)

За счет шаблонных функций вы можете передавать не только параметры, но также типы данных в функцию. За урок вы научитесь создавать шаблонные функций в C++.

Видеоурок

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

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

Пример создания шаблонной функции:

template // Указываем что это шаблон функции // Принимаем параметр value и его тип данных мы получим // также в качестве передаваемого параметра - Type1 void funct (Type1 *value) < cout 

Для передачи нескольких типов данных стоит прописать следующее:

// Здесь мы получаем сразу два типа данных template void funct (Type1 *value, Type2 *newValue) < // Мы можем использовать передаваемые типы в самой функции Type1 someNew; cout 

Для вызова шаблонной функции пропишите следующее:

// Вызов функции с одним параметром funct(value); // Вызов с несколькими параметрами funct(value, oneMoreValue);

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

#include #include using namespace std; template void print_arr(T* arr, int len) < for (T2 i = 0; i < len; i++) cout int main() < setlocale(LC_ALL, "RU"); int arr1[] = < 5, 6, 3, 2, 0, -4 >; print_arr(arr1, 6); float arr2[] = < 5.34f, 6.01f, 3.23f >; print_arr(arr2, 3); return 0; >

Задание к уроку

Необходимо оформить подписку на проект, чтобы получить доступ ко всем домашним заданиям

Большое задание по курсу

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

Шаблоны

Шаблоны классов позволяют определить конструкции (функции, классы), которые используют определенные типы, но на момент написания кода точно не известно, что это будут за типы. Иными словами, шаблоны позволяют определить универсальные конструкции, которые не зависят от определенного типа.

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

Вначале рассмотрим пример, где это может пригодиться. Например, нам надо определить функцию для сложения двух чисел int , double и std::string . Первое, что приходит на ум, сделать перегрузку функции - для каждого типа определить свою версию:

#include int add(int, int); double add(double, double); std::string add(std::string, std::string); int main() < std::cout int add(int x, int y) < return x + y; >double add(double x, double y) < return x + y; >std::string add(std::string str1, std::string str2)

Данный пример отлично работает, производит вычисления, как и должен. Однако в данном случае мы сталкиваемся с тем, что функция add фактически повторяется. Ее версии фактически выполняют одно и то же действие, единственно что отличается тип параметров и возвращаемого значения.

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

#include template T add(T, T); // прототип функции int main() < std::cout template T add(T a, T b)

Определение шаблона функции начинается с ключевого слова template , после которого в угловых скобках идет слово typename и затем список параметров шаблона:

template T add(T a, T b)

В данном случае после typename указан один параметр - T . Параметр шаблона представляет произвольный идентификатор, в качестве которого, как правило, применяются заглавные буквы, например, T. Но это необязательно. То есть в данном случае параметр T будет представлять некоторый тип, который становится известным во время компиляции. Это может быть и тип int, и double, и string, и любой другой тип. Но поскольку внутри функции мы применяем операцию сложения, важно, чтобы тип, который будет применяться вместо параметра T, поддерживал операцию сложения, которая возвращала бы объект этого же типа. Если вдруг используемый тип не будет применять операцию сложения, то на этапе компиляции мы столкнемся с ошибкой.

И при вызове функции add в нее можно передавать объекты и типа int, и типа double, и любого другого типа. При вызове функции компилятор на основании типа аргументов выведет конкретный тип, связанный с параметром шаблона T, и создаст экземпляр функции add, который работает с конкретным типом, и при вызове функции будет вызваться данный экземпляр функции. Если для последующего вызова функции требуется тот же экземпляр, то компилятор использует существующий экземпляр функции.

При этом также можно использовать ссылки, указатели, массивы, которые представляют тип параметра шаблона. Например, в программе выше передадим параметры по ссылке и сделаем их константными:

#include template T add(const T&, const T&); int main() < std::cout template T add(const T& a, const T& b)

Другой пример - функция обмена значениями:

#include template void swap(T&, T&); int main() < int c ; int d ; swap(c, d); std::cout #include template T* max(T*, T*); int main() < int a, b; std::cout , d; std::cout template T* max(T* a, T* b) < return *a >*b? a : b; >

Явный вызов реализации функции для определенного типа

В примерах выше компилятор на основании параметров определял, какой именно тип использует функция. Однако мы можем также явным образом указать, какой тип мы хотим использовать:

#include template T add(const T&, const T&); int main() < double d < add(3.3, 2.2)>; std::cout << "d: " << d << std::endl; //d: 5.5 d = add(3, 2); std::cout template T add(const T& a, const T& b)

Здесь мы явным образом указываем, что мы хотим использовать тип double :

double d < add(3.3, 2.2)>;

При этом во втором случае в функцию передаются целочисленные литералы, однако мы все равно используем реализацию функции для типа double :

d = add(3, 2);

Перегрузка функций и параметризация

С одной стороны, параметризация позволяет снизить возможность перегрузки функций, так как мы можем абстрагироваться от конкретных типов. С другой стороны, все равно могут быть ситуации, когда необходимы разные версии функции. И тут мы можем совместить перегрузку функций и их параметризацию. Например, нам нужно найти максимальное значение из двух, либо из набора элементов:

#include template const T* max(const T*, const T*); template const T* max(const T[], unsigned); int main() < int a, b; std::cout ; std::cout template const T* max(const T* a, const T* b) < return *a >*b? a : b; > template const T* max(const T data[], unsigned size) < const T* result <>; // если вектор пуст, то возвращается nullptr for(unsigned i<>; i < size; i++) < // если result не равен nullptr и *result меньше value if (!result || data[i] >*result) result = &data[i]; > return result; >

Здесь определены две параметризированные функции для нахождения максимального значения. Но одна из них просто сравнивает два значения по адресам в указателях, а вторая проходит по всем элементам массива и выбирает из них наибольший.

Использование нескольких параметров

Можно использовать несколько параметров. Например, нам надо определить функцию перевода некоторой суммы с одного счета на другой и передать код операции. Для идентификаторов счета и кода операции мы можем использовать разные типы - числа, строки и т.д. Для этого определим следующую функцию:

#include template void transfer(T, T, K, int); int main() < transfer("id1234", "id5678", 2804, 5000); >template void transfer(T fromAccount, T toAccount, K code, int sum)

В данном случае при вызове transfer("id1234", "id5678", 2804, 5000); вместо параметра T будет подставляться символьный массив, а вместо параметра K - тип int.

Выведение типов и decltype(auto)

Иногда может быть неизвестно, значение какого именного типа будет возвращаться, или мы захотим предоставить компилятору выводить точный тип возвращаемых данных. В этом случае вместо возвращаемого типа можно использовать заменитель decltype(auto) . Например, определим функцию вычисления среднего арифметического с выведением результата:

#include template decltype(auto) average(const T (&data)[], unsigned size) < T result <>; for(unsigned i<>; i < size; i++) < result += data[i]; >return result / size; > int main() < int numbers[]; std::cout 

Нетипизированные параметры

С++ позволяет определять шаблоны с нетипизированными параметрами, то есть как и в функции, мы можем определять обычные параметры конкретных типов, например:

#include template void print(const T&); int main() < print(3); > // печатаем value N раз template void print(const T& value) < for(unsigned i<>; i < N; i++) < std::cout >

Здесь определен шаблон функции print, которая печатает значение value, которое представляет тип T, N раз. При этом параметр N имеет четко установленный тип - unsigned int . При вызове функции параметру N можно передать значение. Например, напечатаем 4 раза число 3:

print(3);

Большого смысла такие параметры не имеют, поскольку мы равным образом можем определить стандартные параметры в функции. Однако в ряде сценариев они могут оказаться полезными. Например, вычислим длину любого массива:

#include template size_t size(const T (&data)[N]) < return N; >int main() < const int numbers1[]; const double numbers2[]; const char* people[] ; std::cout

Другой пример - в одном из листингов выше для вычисления среднего значения чисел массива в функцию передавался размер массива. Но с помощью нетипизированных параметров мы можем избавиться от необходимости передавать в функцию размер массива:

#include template T average(const T (&)[N]); int main() < int numbers1[]; std::cout ; std::cout template T average(const T (&data)[N]) < T result <>; for(unsigned i<>; i < N; i++) < result += data[i]; >return result / N; >

Здесь для представления размера массива определен параметр N типа size_t . При вызове функции на основе переданного в функцию массива этот параметр получает конкретное значение - размер массива.

Выведение типов и результата

Стоит отметить, что начиная со стандарта C++20 можно определять параметры, типы которых автоматически выводятся исходя из переданных аргументов. Аналогично можно и выводить тип результата. Для этого применяется ключевое слово auto . Равным образом для определения типов параметров и результатов функции можно использовать выражения auto* , auto& и const auto& :

#include > auto add(const auto& a, const auto& b) < return a + b; >int main() < const int n1; const int n2; std::cout ; const double d2; std::cout 

Что такое template, как и зачем?

Шаблоны (template) в C++ являются отдельными синтаксическими конструкциями.

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

Вот простейший пример использования шаблонов:

template T min(const T x, const T y)

Мы написали код, который работает с любым типом данных, требуя от этого типа только наличия операции "меньше" (operator<). (На самом деле ещё конструктора копирования, но это уже детали.) Теперь мы можем использовать наш шаблон:

int x, y; int z = min(x,y); // Автоматический вывод типа. double a, b; double c = min(a,b); // Автоматический вывод типа. double d = min(x,y); // Явное указание типа. std::vector::iterator i, j, k; k = min(i,j); 

Видно, что нам удалось использовать этот шаблон не только к обычным числовым типам, но и к сложным сущностям - итераторам.

В этом мощь обобщённого программирования - код, написанный один раз используется многократно. Но есть и недостаток - "разбухание" бинарного кода - для каждого типа параметра шаблона компилятор создаст свой бинарный код.

Шаблоны (C++)

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

Определение и использование шаблонов

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

template T minimum(const T& lhs, const T& rhs)

Приведенный выше код описывает шаблон универсальной функции с одним параметром типа T, возвращаемое значение и параметры вызова (lhs и rhs) всех этих типов. Вы можете назвать параметр типа, который вы хотите, но по соглашению одноглавные буквы чаще всего используются. T является параметром шаблона; typename ключевое слово говорит, что этот параметр является заполнителем для типа. При вызове функции компилятор заменит каждый экземпляр T конкретным аргументом типа, заданным пользователем или выведенным компилятором. Процесс, в котором компилятор создает класс или функцию из шаблона, называется экземпляром шаблона. minimum Это экземпляр шаблона minimum .

В другом месте пользователь может объявить экземпляр шаблона, специализированного для int. Предположим, что get_a() и get_b() — это функции, возвращающие int:

int a = get_a(); int b = get_b(); int i = minimum(a, b); 

Тем не менее, поскольку это шаблон функции, и компилятор может выводить тип T из аргументов a и b, можно вызвать его так же, как обычная функция:

int i = minimum(a, b); 

При обнаружении последней инструкции компилятор создает новую функцию, в которой каждое вхождение T в шаблоне заменяется следующим int образом:

int minimum(const int& lhs, const int& rhs)

Правила того, как компилятор выполняет вычет типов в шаблонах функций, основаны на правилах для обычных функций. Дополнительные сведения см. в разделе "Разрешение перегрузки вызовов шаблонов функций".

Параметры типа

В приведенном minimum выше шаблоне обратите внимание, что параметр типа T не является соответствующим образом, пока он не будет использоваться в параметрах вызова функции, где добавляются квалификаторы констант и ссылочных квалификаторов.

Нет практического ограничения на количество параметров типа. Разделите несколько параметров запятыми:

template class Foo<>; 

Ключевое слово class эквивалентен этому контексту typename . Вы можете выразить предыдущий пример следующим образом:

template class Foo<>; 

Оператор многоточия (. ) можно использовать для определения шаблона, который принимает произвольное число параметров нулевого или более типа:

template class vtclass; vtclass < >vtinstance1; vtclass vtinstance2; vtclass vtinstance3; 

Любой встроенный или определяемый пользователем тип можно использовать в качестве аргумента типа. Например, можно использовать std::vector в стандартной библиотеке для хранения переменных типа int , double std::string, const MyClass MyClass *, MyClass& и т. д. Основное ограничение при использовании шаблонов заключается в том, что аргумент типа должен поддерживать все операции, применяемые к параметрам типа. Например, если мы вызываем minimum использование MyClass , как в следующем примере:

class MyClass < public: int num; std::wstring description; >; int main() < MyClass mc1 ; MyClass mc2 ; auto result = minimum(mc1, mc2); // Error! C2678 > 

Ошибка компилятора создается, так как MyClass не предоставляет перегрузку для оператора.

Нет никаких обязательных требований, что аргументы типа для любого конкретного шаблона принадлежат одной иерархии объектов, хотя можно определить шаблон, который применяет такое ограничение. Вы можете объединить объектно-ориентированные методы с шаблонами; Например, можно сохранить производный* в векторе. Обратите внимание, что аргументы должны быть указателями

vector vec; MyDerived d(3, L"back again", time(0)); vec.push_back(&d); // or more realistically: vector> vec2; vec2.push_back(make_shared()); 

Основные требования, std::vector которые и другие стандартные контейнеры библиотеки накладывают на элементы T , которые T можно назначить с помощью копирования и создания копирования.

Параметры, не относящиеся к типу

В отличие от универсальных типов на других языках, таких как C# и Java, шаблоны C++ поддерживают параметры, отличные от типов, также называемые параметрами значения. Например, можно указать целочисленное значение константы, чтобы указать длину массива, как и в этом примере, аналогичному классу std::array в стандартной библиотеке:

template class MyArray < T arr[L]; public: MyArray() < . >>; 

Обратите внимание на синтаксис в объявлении шаблона. Значение size_t передается в качестве аргумента шаблона во время компиляции и должно быть const или выражением constexpr . Вы используете его следующим образом:

MyArray arr; 

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

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

В Visual Studio 2017 и более поздних версиях и в /std:c++17 режиме или более поздних версиях компилятор выводит тип аргумента шаблона, отличного от типа, объявленного с помощью auto :

template constexpr auto constant = x; auto v1 = constant; // v1 == 5, decltype(v1) is int auto v2 = constant; // v2 == true, decltype(v2) is bool auto v3 = constant; // v3 == 'a', decltype(v3) is char 

Шаблоны в качестве параметров шаблона

Шаблон может быть параметром шаблона. В этом примере MyClass2 имеет два параметра шаблона: параметр typename T и параметр шаблона Arr:

template class Arr> class MyClass2 < T t; //OK Arra; U u; //Error. U not in scope >; 

Так как сам параметр Arr не имеет текста, его имена параметров не требуются. На самом деле, это ошибка, чтобы ссылаться на имя типа или имена параметров класса Arr из текста MyClass2 . По этой причине имена параметров типа Arr могут быть опущены, как показано в этом примере:

template class Arr> class MyClass2 < T t; //OK Arra; >; 

Аргументы шаблона по умолчанию

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

template > class vector; 

В большинстве случаев класс std::allocator по умолчанию является допустимым, поэтому используется вектор, как показано ниже:

vector myInts; 

Но при необходимости можно указать пользовательский распределитель следующим образом:

vector ints; 

При наличии нескольких аргументов шаблона все аргументы после первого аргумента по умолчанию должны иметь аргументы по умолчанию.

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

template class Bar < //. >; . int main() < Bar<>bar; // use all default type arguments > 

Специализация шаблонов

В некоторых случаях невозможно или желательно определить точно тот же код для любого типа. Например, может потребоваться определить путь к коду, который необходимо выполнить, только если аргумент типа является указателем или std::wstring или типом, производным от определенного базового класса. В таких случаях можно определить специализацию шаблона для конкретного типа. Когда пользователь создает экземпляр шаблона с этим типом, компилятор использует специализацию для создания класса, а для всех других типов компилятор выбирает более общий шаблон. Специализации, в которых все параметры являются специализированными, являются полными специализациями. Если только некоторые из параметров специализированы, он называется частичной специализацией.

template class MyMap; // partial specialization for string keys template class MyMap ; . MyMap classes; // uses original template MyMap classes2; // uses the partial specialization 

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

Дополнительные сведения см. в разделе "Специализация шаблона".

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *