Явная специализация шаблона *
Не всегда удается написать шаблон функции, который годился бы для всех возможных типов, с которыми он может быть конкретизирован. В некоторых случаях имеется специальная информация о типе, позволяющая написать более эффективную функцию, чем конкретизированная по шаблону. А иногда общее определение, предоставляемое шаблоном, для некоторого типа просто не работает. Рассмотрим, например, следующее определение шаблона функции max():
// обобщенное определение шаблона
template <class T>
T max( T t1, T t2 ) {
return ( t1 > t2 ? t1 : t2 );
}
Когда этот шаблон конкретизируется с аргументом типа const char*, то обобщенное определение оказывается семантически некорректным, если мы интерпретируем каждый аргумент как строку символов в смысле языка C, а не как указатель на символ. В этом случае необходимо предоставить специализированное определение для конкретизации шаблона.
Явное определение специализации – это такое определение, в котором за ключевым словом template следует пара угловых скобок <>, а за ними – определение специализированного шаблона. Здесь указывается имя шаблона, аргументы, для которых он специализируется, список параметров функции и ее тело. В следующем примере для max(const char*, const char*) определена явная специализация:
#include <cstring>
// явная специализация для const char*:
// имеет приоритет над конкретизацией шаблона
// по обобщенному определению
typedef const char *PCC;
template<> PCC max< PCC >( PCC s1, PCC s2 ) {
return ( strcmp( s1, s2 ) > 0 ? s1 : s2 );
Поскольку имеется явная специализация, шаблон не будет конкретизирован с типом const char* при вызове в программе функции max(const char*, const char*). При любом обращении к max() с двумя аргументами типа const char* работает специализированное определение. Для любых других обращений функция сначала конкретизируется по обобщенному определению шаблона, а затем вызывается. Вот как это выглядит:
#include <iostream>
// здесь должно быть определение шаблона функции max()
// и его специализации для аргументов const char*
int main() {
// вызов конкретизированной функции: int max< int >( int, int );
int i = max( 10, 5 );
// вызов явной специализации:
// const char* max< const char* >( const char*, const char* );
const char *p = max( "hello", "world" );
cout << "i: " << i << " p: " << p << endl;
return 0;
}
Можно объявлять явную специализацию шаблона функции, не определяя ее. Например, для функции max(const char*, const char*) она объявляется так:
// объявление явной специализации шаблона функции
template< > PCC max< PCC >( PCC, PCC );
При объявлении или определении явной специализации шаблона функции нельзя опускать слово template и следующую за ним пару скобок <>. Кроме того, в объявлении специализации обязательно должен быть список параметров функции:
// ошибка: неправильные объявления специализации
// отсутствует template<>
PCC max< PCC >( PCC, PCC );
// отсутствует список параметров
template<> PCC max< PCC >;
Однако здесь можно опускать задание аргументов шаблона, если они выводятся из формальных параметров функции:
// правильно: аргумент шаблона const char* выводится из типов параметров
template<> PCC max( PCC, PCC );
В следующем примере шаблон функции sum() явно специализирован:
template <class T1, class T2, class T3>
T1 sum( T2 op1, T3 op2 );
// объявления явных специализаций
// ошибка: аргумент шаблона для T1 не может быть выведен;
// он должен быть задан явно
template<> double sum( float, float );
// правильно: аргумент для T1 задан явно,
// T2 и T3 выводятся и оказываются равными float
template<> double sum<double>( float, float );
// правильно: все аргументы заданы явно
template<> int sum<int,char>( char, char );
Пропуск части template<> в объявлении явной специализации не всегда является ошибкой. Например:
// обобщенное определение шаблона
template <class T>
T max( T t1, T t2 ) { /* ... */ }
// правильно: обычное объявление функции
const char* max( const char*, const char*);
Однако эта инструкция не является специализацией шаблона функции. Здесь просто объявляется обычная функция с типом возвращаемого значения и списком параметров, которые соответствуют полученным при конкретизации шаблона. Объявление обычной функции, являющееся конкретизацией шаблона, не считается ошибкой.
Так почему бы просто не объявить обычную функцию? Как было показано в разделе 10.3, для преобразования фактического аргумента функции, конкретизированной по шаблону, в соответствующий формальный параметр в случае, когда этот аргумент принимает участие в выводе аргумента шаблона, может быть применено лишь ограниченное множество преобразований типов. Точно так же обстоит дело и в ситуации, когда шаблон функции специализируется явно: к фактическим аргументам функции при этом тоже применимо лишь ограниченное множество преобразований. Явные специализации не помогают обойти соответствующие ограничения. Если мы хотим выйти за их пределы, то должны определить обычную функцию вместо специализации шаблона. (В разделе 10.8 этот вопрос рассматривается более подробно; там же показано, как работает разрешение перегруженной функции для вызова, который соответствует как обычной функции, так и экземпляру, конкретизированному из шаблона.)
Явную специализацию можно объявлять даже тогда, когда специализируемый шаблон объявлен, но не определен. В предыдущем примере шаблон функции sum() лишь объявлен к моменту специализации. Хотя определение шаблона не обязательно, объявление все же требуется. То, что sum() – шаблон, должно быть известно до того, как это имя может быть специализировано.
Такое объявление должно быть видимо до его использования в исходном файле. Например:
#include <iostream>
#include <cstring>
// обобщенное определение шаблона
template <class T>
T max( T t1, T t2 ) { /* ... */ }
int main() {
// конкретизация функции
// const char* max< const char* >( const char*, const char* );
const char *p = max( "hello", "world" );
cout << "p: " << p << endl;
return 0;
}
// некорректная программа: явная специализация const char *:
// имеет приоритет над обобщенным определением шаблона
typedef const char *PCC;
template<> PCC max< PCC >(PCC s1, PCC s2 ) { /* ... */ }
В предыдущем примере конкретизация max(const char*, const char*) предшествует объявлению явной специализации. Поэтому компилятор имеет право предположить, что функция должна быть конкретизирована по обобщенному определению шаблона. Однако в программе не может одновременно существовать явная специализация и экземпляр, конкретизированный по тому же шаблону с тем же множеством аргументов. Когда в исходном файле после конкретизации встречается явная специализация max(const char*, const char*), компилятор выдает сообщение об ошибке.
Если программа состоит из нескольких файлов, то объявление явной специализации шаблона должно быть видимо в каждом файле, в котором она используется. Не разрешается в одних файлах конкретизировать шаблон функции по обобщенному определению, а в других специализировать с тем же множеством аргументов. Рассмотрим следующий пример:
// --------- max.h -------
// обобщенное определение шаблона
template <class Type>
Type max( Type t1, Type t2 ) { /* ... */ }
// --------- File1.C -------
#include <iostream>
#include "max.h"
void another();
int main() {
// конкретизация функции
// const char* max< const char* >( const char*, const char* );
const char *p = max( "hello", "world" );
cout << "p: " << p << endl;
another();
return 0;
}
// --------- File2.C -------
#include <iostream>
#include <cstring>
#include "max.h"
// явная специализация шаблона для const char*
typedef const char *PCC;
template<> PCC max< PCC >( PCC s1, PCC s2 ) { /* ... */ }
void another() {
// явная специализация
// const char* max< const char* >( const char*, const char* );
const char *p = max( "hi", "again" );
cout << " p: " << p << endl;
return 0;
}
Эта программа состоит из двух файлов. В файле File1.C нет объявления явной специализации max(const char*, const char*). Вместо этого шаблон функции конкретизируется из обобщенного определения. В файле File2.C объявлена явная специализация, и при обращении к max("hi", "again") именно она и вызывается. Поскольку в одной и той же программе функция max(const char*, const char*) то конкретизируется по шаблону, то специализируется явно, компилятор считает программу некорректной. Для исправления этого объявление явной специализации шаблона должно предшествовать вызову функции max(const char*, const char*) в файле File1.C.
Чтобы избежать таких ошибок и гарантировать, что объявление явной специализации шаблона max(const char*, const char*) внесено в каждый файл, где используется шаблон функции max() с аргументами типа const char*, это объявление следует поместить в заголовочный файл "max.h" и включать его во все исходные файлы, в которых используется шаблон max():
// --------- max.h -------
// обобщенное определение шаблона
template <class Type>
Type max( Type t1, Type t2 ) { /* ... */ }
// объявление явной специализации шаблона для const char*
typedef const char *PCC;
template<> PCC max< PCC >( PCC s1, PCC s2 );
// --------- File1.C -------
#include <iostream>
#include "max.h"
void another();
int main() {
// специализация
// const char* max< const char* >( const char*, const char* );
const char *p = max( "hello", "world" );
// ....
}
Упражнение 10.10
Определите шаблон функции count() для подсчета числа появлений некоторого значения в массиве. Напишите вызывающую программу. Последовательно передайте в ней массив значений типа double, int и сhar. Напишите специализированный экземпляр шаблона count() для обработки строк.