Рассмотрим простую функцию, реализующую алгоритм сравнения двух величин типа int:
int min (int iV_1, int iV_2)
{
return iV_1 < iV_2 ? iV_1 : iV_2;
/*
Возвращается значение iV_1, если это значение меньше iV_2.
В противном случае возвращается значение iV_2.
*/
}
Для каждого типа сравниваемых величин должен быть определён собственный вариант функции min(). Вот как эта функция выглядит для типа float:
float min (float fV_1, float fV_2)
{
return fV_1 < fV_2 ? fV_1 : fV_2;
}
Для типа double функция будет выглядеть аналогично.
Мы можем бесконечно упражняться в создании совместно используемых функций, хотя можно воспользоваться средствами препроцессора:
#define min(a,b) ((a)<(b)?(a):(b))
Это определение правильно работает в простых случаях:
min(10, 20);
min(10.0, 20.0);
В более сложных случаях могут получаться неожиданные результаты. Это происходит из-за того, что препроцессор действует независимо от компилятора и производит лишь простую текстовую обработку исходного модуля. Язык C++ предоставляет средство для решения этой задачи. При этом сохраняется присущая макроопределениям краткость и строгость контроля типов языка. Этим средством является шаблон функции.
Шаблон функции позволяет определять семейство функций. Это семейство характеризуется общим алгоритмом, который может применяться к данным различных типов. При этом задание конкретного типа данных для очередного варианта функции обеспечивается специальной синтаксической конструкцией, называемой списком параметров шаблона функции. Объявление функции, которому предшествует список параметров шаблона, называется шаблоном функции.
Итак, объявление и определение шаблона функции начинается ключевым словом template, за которым следует заключённый в угловые скобки и разделённый запятыми непустой список параметров шаблона. Эта часть объявления или определения обычно называется заголовком шаблона.
Каждый параметр шаблона состоит из служебного слова class, за которым следует идентификатор. В контексте объявления шаблона функции служебное слово class не несёт никакой особой смысловой нагрузки. В заголовке шаблона имена параметров шаблона должны быть уникальны.
Следом за заголовком шаблона располагается прототип или определение функции – всё зависит от контекста программы. Как известно, у прототипа и определения функции также имеется собственный заголовок. Этот заголовок состоит из спецификатора возвращаемого значения (вполне возможно, что спецификатором возвращаемого значения может оказаться идентификатор из списка параметров шаблона), имени функции и списка параметров. Все до одного идентификаторы из заголовка шаблона обязаны входить в список параметров функции. В этом списке они играют роль спецификаторов типа. Объявления параметров, у которых в качестве спецификатора типа используется идентификатор из списка параметров шаблона, называется шаблонным параметром. Наряду с шаблонными параметрами в список параметров функции могут также входить параметры основных и производных типов.
В качестве примера рассмотрим программу, в которой для определения минимального значения используется шаблон функции min():
template <class Type>
Type min (Type a, Type b);
/*
Ключевое слово template обозначает начало списка параметров шаблона. Этот список содержит единственный идентификатор Type. Сама функция min() содержит два объявления шаблонных аргументов типа Type. Возвращаемое значение также представлено шаблоном параметра Type.
*/
int main (void)
{
min(10,20); // int min (int, int);
min(10.0,20.0); // float min (float, float);
/*
Вызовы шаблонной функции. На основе выражения вызова (транслятор должен распознать тип параметров) и определения шаблона транслятор самостоятельно создает различные определения шаблонных функций. И только после этого он обеспечивает передачу управления созданной шаблонной функции.
*/
return 0;
}
template <class Type>
Type min (Type a, Type b)
{
return a < b ? a : b;
}
/*
По аналогии с определением функции, эту конструкцию будем называть определением шаблона функции.
*/
Определение шаблона функции заставляет транслятор самостоятельно достраивать определения новых шаблонных функций, а точнее, создавать множество совместно используемых функций, у которых типы параметров и, возможно, тип возвращаемого значения зависит от типа параметров и типа возвращаемого значения в вызовах шаблонной функции. Этот процесс определения называют конкретизацией шаблона функции.
В результате конкретизации шаблона функции min() транслятор строит следующий вариант программы с двумя шаблонными функциями (по выражению вызова на основе шаблона строится шаблонная функция):
int min (int a, int b);
float min (float a, float b);
int main (void)
{
min(10,20);
min(10.0,20.0);
return 0;
}
int min (int a, int b)
{
return a < b ? a : b;
}
float min (float a, float b)
{
return a < b ? a : b;
}
Построение шаблонной функции осуществляется на основе выражений вызова. При этом в качестве значений параметров в выражении вызова могут быть использованы значения любых типов, для которых определены используемые в теле функции операции. Так, для функции min() тип параметров зависит от области определения операции сравнения (<).
По мере того как шаблоны функций становятся более сложными, они могут обеспечить поддержку нескольких типов. Например, программа может создать шаблон для функции с именем array_sort, которая сортирует элементы массива. В данном случае функция может использовать два параметра: первый, соответствующий массиву, и второй, соответствующий количеству элементов массива. Если программа предполагает, что массив никогда не будет содержать более 32767 значений, она может использовать тип int для параметра размера массива. Однако более универсальный шаблон мог бы предоставить программе возможность указать свой собственный тип этого параметра. Например:
template<class Т, class T1>
void array_sort(T array[ ], T1 elements)
{
// операторы
}
С помощью шаблона array_sort программа может создать функции, которые сортируют маленькие массивы типа float (менее 128 элементов) и очень большие массивы типа int, используя следующие прототипы:
void array_sort(float, char);
void array_sort(int, long);