Шаблоны и шаблонные функции в C++. Введение
Шаблонные функции
Давайте рассмотрим простой пример. Допустим, у нас есть функция, которая меняет местами значения двух переменных типа int:
#include <iostream>
void my_swap ( int & first , int & second )
{
int temp ( first ) ;
first = second ;
second = temp ;
}
int main ()
{
int a = 5 ;
int b = 10 ;
std::cout << a << " " << b << std::endl ;
my_swap ( a , b ) ;
std::cout << a << " " << b << std::endl ;
}
Теперь, допустим, у нас в функции main так же есть две переменные типа double, значения которых тоже нужно обменять. Функция для обмена значений двух переменных типа int нам не подойдет. Напишем функцию для double:
void my_swap ( double & first , double & second )
{
double temp ( first ) ;
first = second ;
second = temp ;
}
И теперь перепишем main:
int main () { int a = 5 ; int b = 10 ; std::cout << a << " " << b << std::endl ; my_swap ( a , b ) ; std::cout << a << " " << b << std::endl ; double c = 77.89 ; double d = 54.22 ; std::cout << c << " " << d << std::endl ; my_swap ( c , d ) ; std::cout << c << " " << d << std::endl ; }
Как видите, у нас алгоритм абсолютно одинаковый, отличаются лишь типы параметров и тип переменной temp. А теперь представьте, что нам еще нужны функции для short, long double, char, string и еще множества других типов. Конечно, можно просто скопировать первую функцию, и исправить типы на нужные, тогда получим новую функцию с необходимыми типами. А если функция будет не такая простая? А вдруг потом еще обнаружится, что в первой функции была ошибка? Избежать всего этого можно, например, «шаманством» с препроцессором, но это нам ни к чему, нам помогут шаблоны.
Для начала, заглянем в википедию и посмотрим, что же такое шаблоны:
Шабло́ны (англ. template) — средство языка C++, предназначенное для кодирования обобщённых алгоритмов, без привязки к некоторым параметрам (например, типам данных, размерам буферов, значениям по умолчанию).
https://ru.wikipedia.org/wiki/Шаблоны_C++
Итак, описание шаблона начинается с ключевого слова template за которым в угловых скобках («<» и «>») следует список параметров шаблона. Далее, собственно идет объявление шаблонной сущности (например функция или класс), т. е. имеет вид: template < template-parameter-list > declaration
.
Теперь давайте напишем шаблонную функцию my_swap.
Исходя из упомянутой выше структуры объявления шаблона следует, что наша функция будет выглядеть так: template < параметры_шаблона > описание_функции
.
Напишем функцию:
template < typename T > void my_swap ( T & first , T & second ) { T temp(first) ; first = second ; second = temp ; }
typename в угловых скобках означает, что параметром шаблона будет тип данных. T — имя параметра шаблона. Вместо typename здесь можно использовать слово class: template < class T > В данном контексте ключевые слова typename и class эквивалентны (лично мне больше нравится typename, а кому-то class). Далее, в тексте шаблона везде, где мы используем тип T, вместо T будет проставляться необходимый нам тип.
void my_swap ( T & first , T & second ) //T - тип, указанный в параметре шаблона
{
T temp(first) ; //временная переменная должна быть того же типа, что и параметры
first = second ;
second = temp ;
}
теперь давайте напишем функцию main:
int main ()
{
int a = 5 ;
int b = 10 ;
std::cout << a << " " << b << std::endl ;
my_swap<int> ( a , b ) ;
std::cout << a << " " << b << std::endl ;
double c = 77.89 ;
double d = 54.22 ;
std::cout << c << " " << d << std::endl ;
my_swap<double> ( c , d ) ;
std::cout << c << " " << d << std::endl ;
}
Как видите, после имени функции в угловых скобках мы указываем тип, который нам необходим, он то и будет типом T.
Шаблон — это лишь макет, по которому компилятор самостоятельно будет генерировать код.
При виде такой конструкции: my_swap<тип> компилятор сам создаст функцию my_swap с необходимым типом. Это называется инстанцирование шаблона.
То есть при виде my_swap<int>
компилятор создаст функцию my_swap
в которой T поменяет на int, а при виде my_swap<double>
будет создана функция с типом double.
Если где-то дальше компилятор опять встретит my_swap<int>
, то он ничего генерировать не будет, т.к. код данной функции уже есть(шаблон с данным параметром уже инстанцирован).
Таким образом, если мы инстанцируем этот шаблон три раза с разными типами, то компилятор создаст три разные функции
Вывод типа шаблона исходя из параметров функции
На самом деле, мы можем вызвать функцию my_swap
не указывая тип в угловых скобках. В ряде случаев компилятор может это сделать за вас.
рассмотрим вызов функции без указания типа:
int a = 5 ;
int b = 10 ;
my_swap ( a , b ) ;
Наша шаблонная функция принимает параметры типа T&, основываясь на шаблоне, компилятор видит, что Вы передаете в функцию аргументы типа int, поэтому может самостоятельно определить, что в данном месте имеется ввиду функция my_swap с типом int. Это deducing template arguments. Теперь давайте напишем пример посложнее. Например, программу сортировки массива(будем использовать сортировку «пузырьком»). Естественно, что алгоритм сортировки один и тот же, а вот типы элементов в массиве будут отличаться. Для обменивания значений будем использовать нашу шаблонную функцию my_swap. Приступим:
#include <iostream> template < typename T > void my_swap ( T & first , T & second ) //T - тип, указанный в параметре шаблона { T temp(first) ; //временная переменная должна быть того же типа, что и параметры first = second ; second = temp ; } //Функция будет принимать указатель на данные //и кол-во элементов массива данных //Сам алгоритм сортировки можете посмотреть в Интернете. //Никаких оптимизаций и проверок аргументов применять не будем, нам нужна просто демонстрация. template < class ElementType > //Использовал class, но можно и typename - без разницы void bubbleSort(ElementType * arr, size_t arrSize) { for(size_t i = 0; i < arrSize - 1; ++i) for(size_t j = 0; j < arrSize - 1; ++j) if (arr[j + 1] < arr[j]) my_swap ( arr[j] , arr[j+1] ) ; } template < typename ElementType > void out_array ( const ElementType * arr , size_t arrSize ) { for ( size_t i = 0 ; i < arrSize ; ++i ) std::cout << arr[i] << ' ' ; std::cout << std::endl ; } int main () { const size_t n = 5 ; int arr1 [ n ] = { 10 , 5 , 7 , 3 , 4 } ; double arr2 [ n ] = { 7.62 , 5.56 , 38.0 , 56.0 , 9.0 } ; std::cout << "Source arrays:\n" ; out_array ( arr1 , n ) ;//Компилятор сам выведет параметр шаблона исходя из первого аргумента функции out_array ( arr2 , n ) ; bubbleSort ( arr1 , n ) ; bubbleSort ( arr2 , n ) ; std::cout << "Sorted arrays:\n" ; out_array ( arr1 , n ) ; out_array ( arr2 , n ) ; }
Вывод программы:
Source arrays: 10 5 7 3 4 7.62 5.56 38 56 9 Sorted arrays: 3 4 5 7 10 5.56 7.62 9 38 56
Как видите, компилятор сам генерирует out_array для необходимого типа. Так же он сам генерирует функцию bubbleSort. А в bubbleSort у нас применяется шаблонная функция my_swap, компилятор сгенерирует и её код автоматически. Удобно, не правда ли?
Введение в шаблонные классы
Шаблонными могут быть не только функции. Рассмотрим шаблонные классы. Начнем с простого примера. Мы добавим в наш предыдущий код функцию, которая будет искать максимум и минимум в массиве. При создании функции «упираемся» в проблему — как вернуть два указателя? Можно передать их в функцию в качестве параметров, а можно вернуть объект, который будет содержать в себе два указателя. Первый вариант при большом кол-ве возвращаемых значений приведет к заваливанию функции параметрами, поэтому я предлагаю сделать структуру:
struct my_pointer_pair
{
тип * first ;
тип * second ;
} ;
А какого же типа будут указатели? Можно сделать их void*, но тогда придется постоянно кастовать их к нужному типу, и код станет похож на «Доширак». А что, если сделать эту структуру шаблонной? Попробуем:
template < typename T, typename U > struct my_pointer_pair { T * first ; U * second ; } ;
Теперь компилятор при виде кода my_pointer_pair<тип1,тип2> сам сгенерирует нам код структуры с соответствующими типами. В данном примере указатели у нас будут одинакового типа, но структуру мы сделаем такой, чтобы типы указателей могли быть разными. Это может быть полезно в других примерах (в данном случае я просто хотел показать, что у шаблона может быть не только один параметр).
int main ()
{
my_pointer_pair<int,double> obj = { new int(10) , new double(67.98) } ;//Создаем объект типа my_pointer_pair<int,double>
std::cout << *obj.first << ' ' << *obj.second << std::endl ;
delete obj.first ;
delete obj.second ;
}
Компилятор не будет автоматически определять типы для шаблона класса, поэтому необходимо их указывать самостоятельно.
Теперь давайте напишем код шаблонной функции для поиска максимума и минимума:
//Шаблон наш будет с одним параметром - тип элементов массива (T)
//Возвращаемое значение - объект типа my_pointer_pair< T , T >
//т.е. first и second в my_pointer_pair будут иметь тип T*.
template < typename T >
my_pointer_pair< T , T > my_minmax_elements ( T * arr , size_t arrSize )
{
my_pointer_pair< T , T > result = { 0 , 0 } ;
if ( arr == 0 || arrSize < 1 )
return result ;
result.first = arr ;
result.second = arr ;
for ( size_t i = 1 ; i < arrSize ; ++i )
{
if ( arr[i] < *result.first )
result.first = arr+i ;
if ( arr[i] > *result.second )
result.second = arr+i ;
}
return result ;
}
Теперь мы можем вызывать данную функцию:
my_pointer_pair< int , int > mm = my_minmax_elements ( arr1 , n ) ;
Для классов мы должны явно указывать параметры шаблона. В стандарте C++11, устаревшее ключевое слово auto поменяло свое значение и теперь служит для автоматического вывода типа в зависимости от типа инициализатора, поэтому мы можем написать так:
auto mm = my_minmax_elements ( arr1 , n ) ;
Предлагаю написать еще одну функцию, которая будет выводить объект my_pointer_pair в стандартный поток вывода:
template < typename T1 , typename T2 >
void out_pair ( const my_pointer_pair< T1 , T2 > & mp )
{
if ( mp.first == 0 || mp.second == 0 )
std::cout << "not found" << std::endl ;
else
std::cout << "min = " << *mp.first << " max = " << *mp.second << std::endl ;
}
Теперь соберем всё воедино:
#include <iostream>
template < typename ElementType >
void out_array ( const ElementType * arr , size_t arrSize )
{
for ( size_t i = 0 ; i < arrSize ; ++i )
std::cout << arr[i] << ' ' ;
std::cout << std::endl ;
}
template < typename T, typename U >
struct my_pointer_pair
{
T * first ;
U * second ;
} ;
//Шаблон наш будет с одним параметром - тип элементов массива (T)
//Возвращаемое значение - объект типа my_pointer_pair< T , T >
//т.е. first и second в my_pointer_pair будут иметь тип T*.
template < typename T >
my_pointer_pair< T , T > my_minmax_elements ( T * arr , size_t arrSize )
{
my_pointer_pair< T , T > result = { 0 , 0 } ;
if ( arr == 0 || arrSize < 1 )
return result ;
result.first = arr ;
result.second = arr ;
for ( size_t i = 1 ; i < arrSize ; ++i )
{
if ( arr[i] < *result.first )
result.first = arr+i ;
if ( arr[i] > *result.second )
result.second = arr+i ;
}
return result ;
}
template < typename T >
void out_pair ( const my_pointer_pair< T , T > & mp )
{
if ( mp.first == 0 || mp.second == 0 )
std::cout << "not found" << std::endl ;
else
std::cout << "min = " << *mp.first << " max = " << *mp.second << std::endl ;
}
int main ()
{
const size_t n = 5 ;
int arr1 [ n ] = { 10 , 5 , 7 , 3 , 4 } ;
double arr2 [ n ] = { 7.62 , 5.56 , 38.0 , 56.0 , 9.0 } ;
std::cout << "Arrays:\n" ;
out_array ( arr1 , n ) ;//Компилятор сам выведет параметр шаблона исходя из первого аргумента функии
out_array ( arr2 , n ) ;
out_pair ( my_minmax_elements ( arr1 , n ) ) ;
out_pair ( my_minmax_elements ( arr2 , n ) ) ;
}
Вывод программы:
Arrays: 10 5 7 3 4 7.62 5.56 38 56 9 min = 3 max = 10 min = 5.56 max = 56
Шаблоны и STL
В комплекте с компилятором Вам предоставляется стандартная библиотека шаблонов (Standart Template Library). Она содержит множество шаблонных функций и классов. Например, класс двусвязного списка(list), класс «пара» (pair), функция обмена двух переменных(swap), функции сортировок, динамически расширяемый массив(vector) и т.д. Всё это — шаблоны и Вы можете их использовать. Для небольшого примера возьмем std::vector:
#include <vector>
#include <algorithm>
#include <iostream>
int main ()
{
std::vector <int> arr;
arr.push_back ( 5 ) ; //Добавляем элемент в конец
arr.push_back ( 7 ) ;
arr.push_back ( 3 ) ;
arr.push_back ( 8 ) ;
std::cout << "Source vector:\n" ;
for ( size_t i = 0 , size = arr.size() ; i < size ; ++i )
std::cout << arr[i] << ' ' ;
std::cout << std::endl ;
std::sort ( arr.begin() , arr.end() ) ; //Сортируем вектор
std::cout << "Sorted vector:\n" ;
for ( size_t i = 0 , size = arr.size() ; i < size ; ++i )
std::cout << arr[i] << ' ' ;
std::cout << std::endl ;
}
Заметьте, когда писали std::vector, авторы понятия не имели, элементы какого типа Вы будете хранить.
Шаблоны это слишком большой и мощный инструмент и описать всё в одной статье не представляется возможным. Это было лишь небольшое введение в мир шаблонов. Углубляясь в шаблоны, Вы поразитесь тому, какой мощный это инструмент и какие возможности он предоставляет.
P.S. высказывайте мнение о статье, критику, дополнения/исправления и интересующие вопросы в комментариях.
P.P.S. Просьба вопросы «консоль закрывается, что делать?», «русский язык не показывает. Что делать?», «как работает сортировка?», «что такое size_t», «что такое std::» и им подобные задавать либо в гугл, либо искать на данном сайте в других статьях. Не нужно захламлять комментарии этой чепухой. Если Вы этого не знаете, то может лучше сначала подтянуть свои знания?
Продолжение: Шаблоны в C++ — часть 2.
Шаблоны C++ — это… Что такое Шаблоны C++?
У этого термина существуют и другие значения, см. Шаблон.Шабло́ны (англ. template) — средство языка C++, предназначенное для кодирования обобщённых алгоритмов, без привязки к некоторым параметрам (например, типам данных, размерам буферов, значениям по умолчанию).
В C++ возможно создание шаблонов функций и классов.
Шаблоны позволяют создавать параметризованные классы и функции. Параметром может быть любой тип или значение одного из допустимых типов (целое число, enum, указатель на любой объект с глобально доступным именем). Например, нам нужен какой-то класс:
class SomeClass{ int SomeValue; int SomeArray[20]; ... }
Для одной конкретной цели мы можем использовать этот класс. Но, вдруг, цель немного изменилась, и нужен еще один класс. Теперь нужно 30 элементов массива SomeArray
и вещественный тип SomeValue
и элементов SomeArray
. Тогда мы можем абстрагироваться от конкретных типов и использовать шаблоны с параметрами. Синтаксис: в начале перед объявлением класса напишем слово template
и укажем параметры в угловых скобках. В нашем примере:
template < int ArrayLength, typename SomeValueType > class SomeClass{ SomeValueType SomeValue; SomeValueType SomeArray[ ArrayLength ]; ... }
Тогда для первой модели пишем:
SomeClass < 20, int > SomeVariable;
для второй:
SomeClass < 30, double > SomeVariable2;
Хотя шаблоны предоставляют краткую форму записи участка кода, на самом деле их использование не сокращает исполнимый код, так как для каждого набора параметров компилятор создаёт отдельный экземпляр функции или класса.
Шаблоны функций
Синтаксис описания шаблона
Шаблон функции начинается с ключевого слова template
, за которым в угловых скобках следует список параметров. Затем следует объявление функции:
template< typename T > void sort( T array[], int size ); // прототип: шаблон sort объявлен, но не определён template< typename T > void sort( T array[], int size ) // объявление и определение { T t; for (int i = 0; i < size - 1; i++) for (int j = size - 1; j > i; j--) if (array[j] < array[j-1]) { t = array[j]; array[j] = array[j-1]; array[j-1] = t; } } template< int BufferSize > // целочисленный параметр char* read() { char *Buffer = new char[ BufferSize ]; /* считывание данных */ return Buffer; }
Ключевое слово typename
появилось сравнительно недавно, поэтому стандарт[1] допускает использование class
вместо typename
:
Вместо T допустим любой другой идентификатор.
Пример использования
Простейшим примером служит определение минимума из двух величин.
Если a меньше b то вернуть а, иначе — вернуть b
В отсутствие шаблонов программисту приходится писать отдельные функции для каждого используемого типа данных. Хотя многие языки программирования определяют встроенную функцию минимума для элементарных типов (таких как целые и вещественные числа), такая функция может понадобиться и для сложных (например «время» или «строка») и очень сложных («игрок» в онлайн-игре) объектов.
Так выглядит шаблон функции определения минимума:
template< typename T > T min( T a, T b ) { return a < b ? a : b; }
Для вызова этой функции можно просто использовать её имя:
min( 1, 2 ); min( 'a', 'b' ); min( string( "abc" ), string( "cde" ) );
Вызов шаблонной функции
Вообще говоря, для вызова шаблонной функции, необходимо указать значения для всех параметров шаблона. Для этого после имени шаблона указывается список значений в угловых скобках:
int i[5] = { 5, 4, 3, 2, 1 }; sort< int >( i, 5 ); char c[] = "бвгда"; sort< char >( c, strlen( c ) ); sort< int >( c, 5 ); // ошибка: у sort< int > параметр int[] а не char[] char *ReadString = read< 20 >(); delete [] ReadString; ReadString = read< 30 >();
Для каждого набора параметров компилятор генерирует новый экземпляр функции. Процесс создания нового экземпляра называется инстанцированием шаблона.
В примере выше компилятор создал две специализации шаблона функции sort
(для типов char
и int
) и две специализации шаблона read
(для значений BufferSize
20 и 30). Последнее скорее всего расточительно, так как для каждого возможного значения параметра компилятор будет создавать новые и новые экземпляры функций, которые будут отличаться лишь одной константой.
Выведение значений параметров
В некоторых случаях компилятор может сам вывести (логически определить) значение параметра шаблона функции из аргумента функции. Например, при вызове вышеописанной функции sort
необязательно указывать параметр шаблона (если он совпадает с типом элементов аргумента-массива):
int i[5] = { 5, 4, 3, 2, 1 }; sort( i, i + 5 ); // вызывается sort< int > char c[] = "бвгда"; sort( c, c + strlen( c ) ); // вызывается sort< char >
Возможно выведение и в более сложных случаях.
В случае использования шаблонов классов с целыми параметрами также возможно выведение этих параметров. Например:
template< int size > class IntegerArray { int Array[ size ]; /* ... */ };
template< int size > // Прототип шаблона void PrintArray( IntegerArray< size > array ) { /* ... */ } // Вызов шаблона // Использование объекта шаблона IntegerArray<20> ia; PrintArray( ia );
Правила выведения введены в язык для облегчения использования шаблона и для избежания возможных ошибок, например попытка использования sort< int >
для сортировки массива символов.
Если параметр шаблона можно вывести по нескольким аргументам, то результат выведения должен быть в точности одинаков для всех этих аргументов. Например, следующие вызовы ошибочны:
min (0, 'a'); min (7, 7.0);
Ошибки в шаблонах
Некоторые ошибки в описании шаблона могут быть выявлены уже в месте описания. Эти ошибки не зависят от конкретных параметров. Например:
template< class T > void f( T data ) { T *pt = 7; // ошибка: инициализация указателя целым числом datA = 0; // ошибка: неизвестный идентификатор datA *pt = data // ошибка: нет точки с запятой }
Ошибки, связанные с использованием конкретных параметров шаблона, нельзя выявить до того, как шаблон использован. Например, шаблон min
сам по себе не содержит ошибок, однако использование его с типами, для которых операция '<'
не определена, приведёт к ошибке:
A obj1, obj2; min( obj1, obj2 );
Если ввести операцию '<'
до первого использования шаблона, то ошибка будет устранена. Так проявляется гибкость шаблонов в C++:
friend inline bool operator< ( const A& a1, const A& a2 ) { return a1.a < a2.a; } min( obj1, obj2 );
Шаблоны классов
В классе, реализующем связный список целых чисел, алгоритмы добавления нового элемента списка, поиска нужного элемента не зависят от того, что элементы списка — целые числа. Те же алгоритмы применялись бы и для списка символов, строк, дат, классов игроков, и так далее.
template< class T > class List { /* ... */ public: void Add( const T& Element ); bool Find( const T& Element ); /* ... */ };
Использование шаблонов
Для использования шаблона класса, необходимо указать его параметры:
List<int> li; List<string> ls; li.Add( 17 ); ls.Add( "Hello!" );
Технические подробности
Параметры шаблонов
Параметрами шаблонов могут быть: параметры-типы, параметры обычных типов, параметры-шаблоны.
Для параметров любого типа можно указывать значения по умолчанию.
template< class T1, // параметр-тип typename T2, // параметр-тип int I, // параметр обычного типа T1 DefaultValue, // параметр обычного типа template< class > class T3, // параметр-шаблон class Character = char // параметр по умолчанию >
Параметры-шаблоны
Если в шаблоне класса или функции необходимо использовать один и тот же шаблон, но с разными параметрами, то используются параметры-шаблоны. Например:
template< class Type, template< class > class Container > class CrossReferences { Container< Type > mems; Container< Type* > refs; /* ... */ }; CrossReferences< Date, vector > cr1; CrossReferences< string, set > cr2;
Нельзя использовать шаблоны функций в качестве параметров-шаблонов.
Правила выведения аргументов шаблона функции
Для параметров, которые являются типами (например параметр T функции sort) возможно выведение, если аргумент функции имеет один из следующих типов:
Тип аргумента | Описание |
---|---|
T | Сам тип T , возможно с модификаторами const или volatile .template< class T > T ReturnMe( const T arg ) { return arg; } ReturnMe( 7 ); ReturnMe( 'a' ); |
T* A — константа | Указатель, ссылка или массив элементов типа T .Примером может служить шаблон функции sort, рассмотренный выше |
Templ<T> Templ — имя шаблона класса | В качестве аргумента, функция требует конкретную специализацию некоторого шаблона.#include <vector> template< class T > void sort( vector< T > array ) { /* сортировка */ } vector<int> i; vector<char> c; sort( i ); sort( c ); |
T (*) (args) args — некие аргументы | Указатель на функцию, которая возвращает тип T.template< class T > T* CreateArray( T(*GetValue)(), const int size ) { T *Array = new T[ size ]; for( int i = 0; i < size; i++ ) Array[i] = GetValue(); return Array; } int GetZero() { return 0; } char InputChar() { char c; cin >> c; return c; } int *ArrayOfZeros = CreateArray( GetZero, 20 ); char *String = CreateArray( InputChar, 40 ); |
type T::* type — некий тип Class — некий класс | Указатель на член класса T произвольного типа. Указатель на член типа T произвольного класса. class MyClass { public: int a; }; template< class T > T& IncrementIntegerElement( int T::* Element, T& Object ) { Object.*Element += 1; return Object; } template< class T > T IncrementMyClassElement( T MyClass::* Element, MyClass& Object ) { Object.*Element += 1; return Object.*Element; } MyClass Obj; int n; n = ( IncrementIntegerElement( &MyClass::a, Obj ) ).a; n = IncrementMyClassElement( &MyClass::a, Obj ); |
type (T::*) (args) type — некий тип Class — некий класс args — некие аргументы | Указатель на функцию-член класса T. Указатель на функцию-член некоторого класса, возвращающую тип T. class MyClass { public: int a; int IncrementA(); }; int MyClass::IncrementA() { return ++a; } template< class T > T& CallIntFunction( int (T::* Function)(), T& Object ) { (Object.*Function)(); return Object; } template< class T > T CallMyClassFunction( T (MyClass::* Function)(), MyClass& Object ) { return (Object.*Function)(); } MyClass Obj; int n; n = ( CallIntFunction( &MyClass::IncrementA, Obj ) ).a; n = CallMyClassFunction( &MyClass::IncrementA, Obj ); |
Члены шаблонов классов
Члены шаблона класса являются шаблонами, причём с той же, что и у шаблона класса, параметризацией. В частности это означает, что определение функций-членов следует начинать с заголовка шаблона:
template< class T > class A { void f( T data ); void g( void ); public: A(); }; template< class T > void A<T>::f( T data ); template< class T > void A<T>::g( void );
Внутри области видимости шаблона не нужно повторять спецификатор. Это значит, что например A<T>::A()
— это конструктор, хотя можно писать и A<T>::A<T>()
.
Типы как члены классов
Если параметром шаблона является класс, у которого есть член, являющийся типом данных, то для использования этого члена, нужно применять ключевое слово typename
. Например:
class Container { public: int array[ 15 ]; typedef int* iterator; /* ... */ iterator begin() { return array; } }; template< class C > void f( C& vector ) { C::iterator i = vector.begin(); // ошибка typename C::iterator i = vector.begin(); }
Шаблоны как члены классов
Проблемы возникают и с членами-шаблонами. Если шаблон, который является членом класса, который в свою очередь является параметром шаблона, используется в этом шаблоне и не допускает выведения параметров, то необходимо использовать квалификатор template
:
class A { /* ... */ public: template< class T > T& ConvertTo(); template< class T > void ConvertFrom( const T& data ); }; template< class T > void f( T Container ) { int i1 = Container.template ConvertTo<int>() + 1; Container.ConvertFrom( i1 ); // квалификатор не нужен }
Шаблоны в других языках программирования
Язык Ада обладает механизмами, похожими на шаблоны.
Язык D обладает шаблонами, местами более мощными, чем C++. [2]
В Java 5 был введен похожий механизм — generic
.
Delphi поддерживает механизм generic, начиная с версии 2009.
См. также
Примечания
Литература
- Дэвид Вандевурд, Николай М. Джосаттис Шаблоны C++: справочник разработчика = C++ Templates: The Complete Guide. — М.: «Вильямс», 2003. — С. 544. — ISBN 0-201-73484-2
- Подбельский В. В. 6.9. Шаблоны функций //Глава 6. Функции, указатели, ссылки // Язык Си++ / рец. Дадаев Ю. Г.. — 4. — М.: Финансы и статистика, 2003. — С. 230-236. — 560 с. — ISBN 5-279-02204-7, УДК 004.438Си(075.8) ББК 32.973.26-018 1я173
Ссылки
Шаблоны C++ — это… Что такое Шаблоны C++?
У этого термина существуют и другие значения, см. Шаблон.Шабло́ны (англ. template) — средство языка C++, предназначенное для кодирования обобщённых алгоритмов, без привязки к некоторым параметрам (например, типам данных, размерам буферов, значениям по умолчанию).
В C++ возможно создание шаблонов функций и классов.
Шаблоны позволяют создавать параметризованные классы и функции. Параметром может быть любой тип или значение одного из допустимых типов (целое число, enum, указатель на любой объект с глобально доступным именем). Например, нам нужен какой-то класс:
class SomeClass{ int SomeValue; int SomeArray[20]; ... }
Для одной конкретной цели мы можем использовать этот класс. Но, вдруг, цель немного изменилась, и нужен еще один класс. Теперь нужно 30 элементов массива SomeArray
и вещественный тип SomeValue
и элементов SomeArray
. Тогда мы можем абстрагироваться от конкретных типов и использовать шаблоны с параметрами. Синтаксис: в начале перед объявлением класса напишем слово template
и укажем параметры в угловых скобках. В нашем примере:
template < int ArrayLength, typename SomeValueType > class SomeClass{ SomeValueType SomeValue; SomeValueType SomeArray[ ArrayLength ]; ... }
Тогда для первой модели пишем:
SomeClass < 20, int > SomeVariable;
для второй:
SomeClass < 30, double > SomeVariable2;
Хотя шаблоны предоставляют краткую форму записи участка кода, на самом деле их использование не сокращает исполнимый код, так как для каждого набора параметров компилятор создаёт отдельный экземпляр функции или класса.
Шаблоны функций
Синтаксис описания шаблона
Шаблон функции начинается с ключевого слова template
, за которым в угловых скобках следует список параметров. Затем следует объявление функции:
template< typename T > void sort( T array[], int size ); // прототип: шаблон sort объявлен, но не определён template< typename T > void sort( T array[], int size ) // объявление и определение { T t; for (int i = 0; i < size - 1; i++) for (int j = size - 1; j > i; j--) if (array[j] < array[j-1]) { t = array[j]; array[j] = array[j-1]; array[j-1] = t; } } template< int BufferSize > // целочисленный параметр char* read() { char *Buffer = new char[ BufferSize ]; /* считывание данных */ return Buffer; }
Ключевое слово typename
появилось сравнительно недавно, поэтому стандарт[1] допускает использование class
вместо typename
:
Вместо T допустим любой другой идентификатор.
Пример использования
Простейшим примером служит определение минимума из двух величин.
Если a меньше b то вернуть а, иначе — вернуть b
В отсутствие шаблонов программисту приходится писать отдельные функции для каждого используемого типа данных. Хотя многие языки программирования определяют встроенную функцию минимума для элементарных типов (таких как целые и вещественные числа), такая функция может понадобиться и для сложных (например «время» или «строка») и очень сложных («игрок» в онлайн-игре) объектов.
Так выглядит шаблон функции определения минимума:
template< typename T > T min( T a, T b ) { return a < b ? a : b; }
Для вызова этой функции можно просто использовать её имя:
min( 1, 2 ); min( 'a', 'b' ); min( string( "abc" ), string( "cde" ) );
Вызов шаблонной функции
Вообще говоря, для вызова шаблонной функции, необходимо указать значения для всех параметров шаблона. Для этого после имени шаблона указывается список значений в угловых скобках:
int i[5] = { 5, 4, 3, 2, 1 }; sort< int >( i, 5 ); char c[] = "бвгда"; sort< char >( c, strlen( c ) ); sort< int >( c, 5 ); // ошибка: у sort< int > параметр int[] а не char[] char *ReadString = read< 20 >(); delete [] ReadString; ReadString = read< 30 >();
Для каждого набора параметров компилятор генерирует новый экземпляр функции. Процесс создания нового экземпляра называется инстанцированием шаблона.
В примере выше компилятор создал две специализации шаблона функции sort
(для типов char
и int
) и две специализации шаблона read
(для значений BufferSize
20 и 30). Последнее скорее всего расточительно, так как для каждого возможного значения параметра компилятор будет создавать новые и новые экземпляры функций, которые будут отличаться лишь одной константой.
Выведение значений параметров
В некоторых случаях компилятор может сам вывести (логически определить) значение параметра шаблона функции из аргумента функции. Например, при вызове вышеописанной функции sort
необязательно указывать параметр шаблона (если он совпадает с типом элементов аргумента-массива):
int i[5] = { 5, 4, 3, 2, 1 }; sort( i, i + 5 ); // вызывается sort< int > char c[] = "бвгда"; sort( c, c + strlen( c ) ); // вызывается sort< char >
Возможно выведение и в более сложных случаях.
В случае использования шаблонов классов с целыми параметрами также возможно выведение этих параметров. Например:
template< int size > class IntegerArray { int Array[ size ]; /* ... */ };
template< int size > // Прототип шаблона void PrintArray( IntegerArray< size > array ) { /* ... */ } // Вызов шаблона // Использование объекта шаблона IntegerArray<20> ia; PrintArray( ia );
Правила выведения введены в язык для облегчения использования шаблона и для избежания возможных ошибок, например попытка использования sort< int >
для сортировки массива символов.
Если параметр шаблона можно вывести по нескольким аргументам, то результат выведения должен быть в точности одинаков для всех этих аргументов. Например, следующие вызовы ошибочны:
min (0, 'a'); min (7, 7.0);
Ошибки в шаблонах
Некоторые ошибки в описании шаблона могут быть выявлены уже в месте описания. Эти ошибки не зависят от конкретных параметров. Например:
template< class T > void f( T data ) { T *pt = 7; // ошибка: инициализация указателя целым числом datA = 0; // ошибка: неизвестный идентификатор datA *pt = data // ошибка: нет точки с запятой }
Ошибки, связанные с использованием конкретных параметров шаблона, нельзя выявить до того, как шаблон использован. Например, шаблон min
сам по себе не содержит ошибок, однако использование его с типами, для которых операция '<'
не определена, приведёт к ошибке:
A obj1, obj2; min( obj1, obj2 );
Если ввести операцию '<'
до первого использования шаблона, то ошибка будет устранена. Так проявляется гибкость шаблонов в C++:
friend inline bool operator< ( const A& a1, const A& a2 ) { return a1.a < a2.a; } min( obj1, obj2 );
Шаблоны классов
В классе, реализующем связный список целых чисел, алгоритмы добавления нового элемента списка, поиска нужного элемента не зависят от того, что элементы списка — целые числа. Те же алгоритмы применялись бы и для списка символов, строк, дат, классов игроков, и так далее.
template< class T > class List { /* ... */ public: void Add( const T& Element ); bool Find( const T& Element ); /* ... */ };
Использование шаблонов
Для использования шаблона класса, необходимо указать его параметры:
List<int> li; List<string> ls; li.Add( 17 ); ls.Add( "Hello!" );
Технические подробности
Параметры шаблонов
Параметрами шаблонов могут быть: параметры-типы, параметры обычных типов, параметры-шаблоны.
Для параметров любого типа можно указывать значения по умолчанию.
template< class T1, // параметр-тип typename T2, // параметр-тип int I, // параметр обычного типа T1 DefaultValue, // параметр обычного типа template< class > class T3, // параметр-шаблон class Character = char // параметр по умолчанию >
Параметры-шаблоны
Если в шаблоне класса или функции необходимо использовать один и тот же шаблон, но с разными параметрами, то используются параметры-шаблоны. Например:
template< class Type, template< class > class Container > class CrossReferences { Container< Type > mems; Container< Type* > refs; /* ... */ }; CrossReferences< Date, vector > cr1; CrossReferences< string, set > cr2;
Нельзя использовать шаблоны функций в качестве параметров-шаблонов.
Правила выведения аргументов шаблона функции
Для параметров, которые являются типами (например параметр T функции sort) возможно выведение, если аргумент функции имеет один из следующих типов:
Тип аргумента | Описание |
---|---|
T | Сам тип T , возможно с модификаторами const или volatile .template< class T > T ReturnMe( const T arg ) { return arg; } ReturnMe( 7 ); ReturnMe( 'a' ); |
T* A — константа | Указатель, ссылка или массив элементов типа T .Примером может служить шаблон функции sort, рассмотренный выше |
Templ<T> Templ — имя шаблона класса | В качестве аргумента, функция требует конкретную специализацию некоторого шаблона.#include <vector> template< class T > void sort( vector< T > array ) { /* сортировка */ } vector<int> i; vector<char> c; sort( i ); sort( c ); |
T (*) (args) args — некие аргументы | Указатель на функцию, которая возвращает тип T.template< class T > T* CreateArray( T(*GetValue)(), const int size ) { T *Array = new T[ size ]; for( int i = 0; i < size; i++ ) Array[i] = GetValue(); return Array; } int GetZero() { return 0; } char InputChar() { char c; cin >> c; return c; } int *ArrayOfZeros = CreateArray( GetZero, 20 ); char *String = CreateArray( InputChar, 40 ); |
type T::* type — некий тип Class — некий класс | Указатель на член класса T произвольного типа. Указатель на член типа T произвольного класса. class MyClass { public: int a; }; template< class T > T& IncrementIntegerElement( int T::* Element, T& Object ) { Object.*Element += 1; return Object; } template< class T > T IncrementMyClassElement( T MyClass::* Element, MyClass& Object ) { Object.*Element += 1; return Object.*Element; } MyClass Obj; int n; n = ( IncrementIntegerElement( &MyClass::a, Obj ) ).a; n = IncrementMyClassElement( &MyClass::a, Obj ); |
type (T::*) (args) type — некий тип Class — некий класс args — некие аргументы | Указатель на функцию-член класса T. Указатель на функцию-член некоторого класса, возвращающую тип T. class MyClass { public: int a; int IncrementA(); }; int MyClass::IncrementA() { return ++a; } template< class T > T& CallIntFunction( int (T::* Function)(), T& Object ) { (Object.*Function)(); return Object; } template< class T > T CallMyClassFunction( T (MyClass::* Function)(), MyClass& Object ) { return (Object.*Function)(); } MyClass Obj; int n; n = ( CallIntFunction( &MyClass::IncrementA, Obj ) ).a; n = CallMyClassFunction( &MyClass::IncrementA, Obj ); |
Члены шаблонов классов
Члены шаблона класса являются шаблонами, причём с той же, что и у шаблона класса, параметризацией. В частности это означает, что определение функций-членов следует начинать с заголовка шаблона:
template< class T > class A { void f( T data ); void g( void ); public: A(); }; template< class T > void A<T>::f( T data ); template< class T > void A<T>::g( void );
Внутри области видимости шаблона не нужно повторять спецификатор. Это значит, что например A<T>::A()
— это конструктор, хотя можно писать и A<T>::A<T>()
.
Типы как члены классов
Если параметром шаблона является класс, у которого есть член, являющийся типом данных, то для использования этого члена, нужно применять ключевое слово typename
. Например:
class Container { public: int array[ 15 ]; typedef int* iterator; /* ... */ iterator begin() { return array; } }; template< class C > void f( C& vector ) { C::iterator i = vector.begin(); // ошибка typename C::iterator i = vector.begin(); }
Шаблоны как члены классов
Проблемы возникают и с членами-шаблонами. Если шаблон, который является членом класса, который в свою очередь является параметром шаблона, используется в этом шаблоне и не допускает выведения параметров, то необходимо использовать квалификатор template
:
class A { /* ... */ public: template< class T > T& ConvertTo(); template< class T > void ConvertFrom( const T& data ); }; template< class T > void f( T Container ) { int i1 = Container.template ConvertTo<int>() + 1; Container.ConvertFrom( i1 ); // квалификатор не нужен }
Шаблоны в других языках программирования
Язык Ада обладает механизмами, похожими на шаблоны.
Язык D обладает шаблонами, местами более мощными, чем C++. [2]
В Java 5 был введен похожий механизм — generic
.
Delphi поддерживает механизм generic, начиная с версии 2009.
См. также
Примечания
Литература
- Дэвид Вандевурд, Николай М. Джосаттис Шаблоны C++: справочник разработчика = C++ Templates: The Complete Guide. — М.: «Вильямс», 2003. — С. 544. — ISBN 0-201-73484-2
- Подбельский В. В. 6.9. Шаблоны функций //Глава 6. Функции, указатели, ссылки // Язык Си++ / рец. Дадаев Ю. Г.. — 4. — М.: Финансы и статистика, 2003. — С. 230-236. — 560 с. — ISBN 5-279-02204-7, УДК 004.438Си(075.8) ББК 32.973.26-018 1я173
Ссылки
Шаблоны C++ — это… Что такое Шаблоны C++?
У этого термина существуют и другие значения, см. Шаблон.Шабло́ны (англ. template) — средство языка C++, предназначенное для кодирования обобщённых алгоритмов, без привязки к некоторым параметрам (например, типам данных, размерам буферов, значениям по умолчанию).
В C++ возможно создание шаблонов функций и классов.
Шаблоны позволяют создавать параметризованные классы и функции. Параметром может быть любой тип или значение одного из допустимых типов (целое число, enum, указатель на любой объект с глобально доступным именем). Например, нам нужен какой-то класс:
class SomeClass{ int SomeValue; int SomeArray[20]; ... }
Для одной конкретной цели мы можем использовать этот класс. Но, вдруг, цель немного изменилась, и нужен еще один класс. Теперь нужно 30 элементов массива SomeArray
и вещественный тип SomeValue
и элементов SomeArray
. Тогда мы можем абстрагироваться от конкретных типов и использовать шаблоны с параметрами. Синтаксис: в начале перед объявлением класса напишем слово template
и укажем параметры в угловых скобках. В нашем примере:
template < int ArrayLength, typename SomeValueType > class SomeClass{ SomeValueType SomeValue; SomeValueType SomeArray[ ArrayLength ]; ... }
Тогда для первой модели пишем:
SomeClass < 20, int > SomeVariable;
для второй:
SomeClass < 30, double > SomeVariable2;
Хотя шаблоны предоставляют краткую форму записи участка кода, на самом деле их использование не сокращает исполнимый код, так как для каждого набора параметров компилятор создаёт отдельный экземпляр функции или класса.
Шаблоны функций
Синтаксис описания шаблона
Шаблон функции начинается с ключевого слова template
, за которым в угловых скобках следует список параметров. Затем следует объявление функции:
template< typename T > void sort( T array[], int size ); // прототип: шаблон sort объявлен, но не определён template< typename T > void sort( T array[], int size ) // объявление и определение { T t; for (int i = 0; i < size - 1; i++) for (int j = size - 1; j > i; j--) if (array[j] < array[j-1]) { t = array[j]; array[j] = array[j-1]; array[j-1] = t; } } template< int BufferSize > // целочисленный параметр char* read() { char *Buffer = new char[ BufferSize ]; /* считывание данных */ return Buffer; }
Ключевое слово typename
появилось сравнительно недавно, поэтому стандарт[1] допускает использование class
вместо typename
:
Вместо T допустим любой другой идентификатор.
Пример использования
Простейшим примером служит определение минимума из двух величин.
Если a меньше b то вернуть а, иначе — вернуть b
В отсутствие шаблонов программисту приходится писать отдельные функции для каждого используемого типа данных. Хотя многие языки программирования определяют встроенную функцию минимума для элементарных типов (таких как целые и вещественные числа), такая функция может понадобиться и для сложных (например «время» или «строка») и очень сложных («игрок» в онлайн-игре) объектов.
Так выглядит шаблон функции определения минимума:
template< typename T > T min( T a, T b ) { return a < b ? a : b; }
Для вызова этой функции можно просто использовать её имя:
min( 1, 2 ); min( 'a', 'b' ); min( string( "abc" ), string( "cde" ) );
Вызов шаблонной функции
Вообще говоря, для вызова шаблонной функции, необходимо указать значения для всех параметров шаблона. Для этого после имени шаблона указывается список значений в угловых скобках:
int i[5] = { 5, 4, 3, 2, 1 }; sort< int >( i, 5 ); char c[] = "бвгда"; sort< char >( c, strlen( c ) ); sort< int >( c, 5 ); // ошибка: у sort< int > параметр int[] а не char[] char *ReadString = read< 20 >(); delete [] ReadString; ReadString = read< 30 >();
Для каждого набора параметров компилятор генерирует новый экземпляр функции. Процесс создания нового экземпляра называется инстанцированием шаблона.
В примере выше компилятор создал две специализации шаблона функции sort
(для типов char
и int
) и две специализации шаблона read
(для значений BufferSize
20 и 30). Последнее скорее всего расточительно, так как для каждого возможного значения параметра компилятор будет создавать новые и новые экземпляры функций, которые будут отличаться лишь одной константой.
Выведение значений параметров
В некоторых случаях компилятор может сам вывести (логически определить) значение параметра шаблона функции из аргумента функции. Например, при вызове вышеописанной функции sort
необязательно указывать параметр шаблона (если он совпадает с типом элементов аргумента-массива):
int i[5] = { 5, 4, 3, 2, 1 }; sort( i, i + 5 ); // вызывается sort< int > char c[] = "бвгда"; sort( c, c + strlen( c ) ); // вызывается sort< char >
Возможно выведение и в более сложных случаях.
В случае использования шаблонов классов с целыми параметрами также возможно выведение этих параметров. Например:
template< int size > class IntegerArray { int Array[ size ]; /* ... */ };
template< int size > // Прототип шаблона void PrintArray( IntegerArray< size > array ) { /* ... */ } // Вызов шаблона // Использование объекта шаблона IntegerArray<20> ia; PrintArray( ia );
Правила выведения введены в язык для облегчения использования шаблона и для избежания возможных ошибок, например попытка использования sort< int >
для сортировки массива символов.
Если параметр шаблона можно вывести по нескольким аргументам, то результат выведения должен быть в точности одинаков для всех этих аргументов. Например, следующие вызовы ошибочны:
min (0, 'a'); min (7, 7.0);
Ошибки в шаблонах
Некоторые ошибки в описании шаблона могут быть выявлены уже в месте описания. Эти ошибки не зависят от конкретных параметров. Например:
template< class T > void f( T data ) { T *pt = 7; // ошибка: инициализация указателя целым числом datA = 0; // ошибка: неизвестный идентификатор datA *pt = data // ошибка: нет точки с запятой }
Ошибки, связанные с использованием конкретных параметров шаблона, нельзя выявить до того, как шаблон использован. Например, шаблон min
сам по себе не содержит ошибок, однако использование его с типами, для которых операция '<'
не определена, приведёт к ошибке:
A obj1, obj2; min( obj1, obj2 );
Если ввести операцию '<'
до первого использования шаблона, то ошибка будет устранена. Так проявляется гибкость шаблонов в C++:
friend inline bool operator< ( const A& a1, const A& a2 ) { return a1.a < a2.a; } min( obj1, obj2 );
Шаблоны классов
В классе, реализующем связный список целых чисел, алгоритмы добавления нового элемента списка, поиска нужного элемента не зависят от того, что элементы списка — целые числа. Те же алгоритмы применялись бы и для списка символов, строк, дат, классов игроков, и так далее.
template< class T > class List { /* ... */ public: void Add( const T& Element ); bool Find( const T& Element ); /* ... */ };
Использование шаблонов
Для использования шаблона класса, необходимо указать его параметры:
List<int> li; List<string> ls; li.Add( 17 ); ls.Add( "Hello!" );
Технические подробности
Параметры шаблонов
Параметрами шаблонов могут быть: параметры-типы, параметры обычных типов, параметры-шаблоны.
Для параметров любого типа можно указывать значения по умолчанию.
template< class T1, // параметр-тип typename T2, // параметр-тип int I, // параметр обычного типа T1 DefaultValue, // параметр обычного типа template< class > class T3, // параметр-шаблон class Character = char // параметр по умолчанию >
Параметры-шаблоны
Если в шаблоне класса или функции необходимо использовать один и тот же шаблон, но с разными параметрами, то используются параметры-шаблоны. Например:
template< class Type, template< class > class Container > class CrossReferences { Container< Type > mems; Container< Type* > refs; /* ... */ }; CrossReferences< Date, vector > cr1; CrossReferences< string, set > cr2;
Нельзя использовать шаблоны функций в качестве параметров-шаблонов.
Правила выведения аргументов шаблона функции
Для параметров, которые являются типами (например параметр T функции sort) возможно выведение, если аргумент функции имеет один из следующих типов:
Тип аргумента | Описание |
---|---|
T | Сам тип T , возможно с модификаторами const или volatile .template< class T > T ReturnMe( const T arg ) { return arg; } ReturnMe( 7 ); ReturnMe( 'a' ); |
T* A — константа | Указатель, ссылка или массив элементов типа T .Примером может служить шаблон функции sort, рассмотренный выше |
Templ<T> Templ — имя шаблона класса | В качестве аргумента, функция требует конкретную специализацию некоторого шаблона.#include <vector> template< class T > void sort( vector< T > array ) { /* сортировка */ } vector<int> i; vector<char> c; sort( i ); sort( c ); |
T (*) (args) args — некие аргументы | Указатель на функцию, которая возвращает тип T.template< class T > T* CreateArray( T(*GetValue)(), const int size ) { T *Array = new T[ size ]; for( int i = 0; i < size; i++ ) Array[i] = GetValue(); return Array; } int GetZero() { return 0; } char InputChar() { char c; cin >> c; return c; } int *ArrayOfZeros = CreateArray( GetZero, 20 ); char *String = CreateArray( InputChar, 40 ); |
type T::* type — некий тип Class — некий класс | Указатель на член класса T произвольного типа. Указатель на член типа T произвольного класса. class MyClass { public: int a; }; template< class T > T& IncrementIntegerElement( int T::* Element, T& Object ) { Object.*Element += 1; return Object; } template< class T > T IncrementMyClassElement( T MyClass::* Element, MyClass& Object ) { Object.*Element += 1; return Object.*Element; } MyClass Obj; int n; n = ( IncrementIntegerElement( &MyClass::a, Obj ) ).a; n = IncrementMyClassElement( &MyClass::a, Obj ); |
type (T::*) (args) type — некий тип Class — некий класс args — некие аргументы | Указатель на функцию-член класса T. Указатель на функцию-член некоторого класса, возвращающую тип T. class MyClass { public: int a; int IncrementA(); }; int MyClass::IncrementA() { return ++a; } template< class T > T& CallIntFunction( int (T::* Function)(), T& Object ) { (Object.*Function)(); return Object; } template< class T > T CallMyClassFunction( T (MyClass::* Function)(), MyClass& Object ) { return (Object.*Function)(); } MyClass Obj; int n; n = ( CallIntFunction( &MyClass::IncrementA, Obj ) ).a; n = CallMyClassFunction( &MyClass::IncrementA, Obj ); |
Члены шаблонов классов
Члены шаблона класса являются шаблонами, причём с той же, что и у шаблона класса, параметризацией. В частности это означает, что определение функций-членов следует начинать с заголовка шаблона:
template< class T > class A { void f( T data ); void g( void ); public: A(); }; template< class T > void A<T>::f( T data ); template< class T > void A<T>::g( void );
Внутри области видимости шаблона не нужно повторять спецификатор. Это значит, что например A<T>::A()
— это конструктор, хотя можно писать и A<T>::A<T>()
.
Типы как члены классов
Если параметром шаблона является класс, у которого есть член, являющийся типом данных, то для использования этого члена, нужно применять ключевое слово typename
. Например:
class Container { public: int array[ 15 ]; typedef int* iterator; /* ... */ iterator begin() { return array; } }; template< class C > void f( C& vector ) { C::iterator i = vector.begin(); // ошибка typename C::iterator i = vector.begin(); }
Шаблоны как члены классов
Проблемы возникают и с членами-шаблонами. Если шаблон, который является членом класса, который в свою очередь является параметром шаблона, используется в этом шаблоне и не допускает выведения параметров, то необходимо использовать квалификатор template
:
class A { /* ... */ public: template< class T > T& ConvertTo(); template< class T > void ConvertFrom( const T& data ); }; template< class T > void f( T Container ) { int i1 = Container.template ConvertTo<int>() + 1; Container.ConvertFrom( i1 ); // квалификатор не нужен }
Шаблоны в других языках программирования
Язык Ада обладает механизмами, похожими на шаблоны.
Язык D обладает шаблонами, местами более мощными, чем C++. [2]
В Java 5 был введен похожий механизм — generic
.
Delphi поддерживает механизм generic, начиная с версии 2009.
См. также
Примечания
Литература
- Дэвид Вандевурд, Николай М. Джосаттис Шаблоны C++: справочник разработчика = C++ Templates: The Complete Guide. — М.: «Вильямс», 2003. — С. 544. — ISBN 0-201-73484-2
- Подбельский В. В. 6.9. Шаблоны функций //Глава 6. Функции, указатели, ссылки // Язык Си++ / рец. Дадаев Ю. Г.. — 4. — М.: Финансы и статистика, 2003. — С. 230-236. — 560 с. — ISBN 5-279-02204-7, УДК 004.438Си(075.8) ББК 32.973.26-018 1я173