Листинг 11.5. Специализации шаблона
template<class Т, class U> // первичный шаблон
// U = int // T = int // Т = U
// частичная специализация для указателей //1=1*, U = void* // полная специализация
class S
{ public: void print(); }: // переопределяемый метод
// частичные специализации шаблона template<class Т > class S<T, int> { public: void print(); }; template<class U> class S<int, T> { public: void print(); }: template<class T> class S<T, T> { public: void print(); }: template<class T, class U> class S<T*. U*> { public: void print(): }; template<class T> class S<T*. void*> { public: void print(); }: template<>
class S<int*. double> { public: void print(); };
Специализации определяются наличием аргументов в угловых скобках после имени класса. Полную специализацию компилятор определяет по пустым угловым скобкам <> после слова template.
Наличие специализаций класса S ставит вопрос о том, какая конкретная версия будет использоваться при вызове метода print(). Ответ почти очевиден: из нескольких подходящих специализаций выбирается «наиболее специализированная». Например:
S<float, .double>().print(): // первичный шаблон
S<char, char>() .printO : // Т = U
S<int, char*>().printO: // T = int
Здесь в первом вызове используется первичный шаблон, второй вызов соответствует шаблону со специализацией Т = U, третий — сопоставляется со специализацией Т = int.
Однако со специализациями шаблонов нужно быть осторожным. Например: S<int, int>() .printO ;
Этот вызов является неоднозначным — ему соответствует аж три возможных специализации: Т = i nt, U = intHT = U. Аналогично, неоднозначными будут, например, вызовы
S<int, void*>() .printO ; S<int*. void*>() .printO ; S<int*. int*>() .printO ;
ВНИМАНИЕ
Круглые скобки перед точкой означают вызов конструктора по умолчанию. В данном случае каждый вызов выполняется для анонимного временного объекта.
Наиболее типичным видом специализации является специализация шаблона для указателей. Образец (см. с. 393 в [2]) специализации <T*,U*> после имени означает, что эта специализация должна использоваться во всех случаях, когда аргументом шаблона является указатель любого типа, jcpoMe void*, для которого реализована более «специализированная» версия <Т*. void*>, например:
S<int*, char*>() .printO ; S<int*, void*>() .printO :
В первом случае используется специализация с аргументами <T*,U*>, а во втором — более специализированная версия <Т*, void*>.
Чтобы специализировать шаблон, необязательно иметь полное определение первичного шаблона — достаточно объявления, например:
template <class Т, class U> class S; // объявление первичного шабпона
template <typename T> class S<T*, void*> // частичная специализация
{//... };
templateo class S<int, void *> {//...
};
// полная специапизация
Все будет корректно работать, если, мы не будем пытаться инстанцировать первичный шаблон.
Специализировать шаблон можно и по параметрам, не являющимся типами,
например:
template<int n, int m> struct A {}; template<int n> struct A<n,n> {};
// первичный шаблон // специапизация n = m
Нужно подчеркнуть, что специализация — это не наследование. Первичный шаблон и его специализированная версия — это два разных шаблонных класса. Полная специализация шаблона вообще представляет собой реализованный на основе первичного шаблона независимый класс. Поэтому полагаться на то, что в специализированной версии «окажутся» методы, реализованные в первичном шаблоне, нельзя. Пусть в первичном шаблоне определен некоторый метод:
void f(void);
Если в специализированной версии этот метод не определен, то попытки вызвать метод первичного шаблона для класса специализированной версии приведут к ошибкам трансляции.
|