16.1.3. Параметры шаблона
Подобно именам параметров функций, имена параметров шаблона не имеют никакого значения. Обычно параметрам типа присваивают имя Т, но можно использовать любое другое:
template <typename Foo> Foo calc(const Foo& a, const Foo& b) {
Foo tmp = a; // тип tmp совпадает с типом параметров и возвращаемого
// значения
// ...
return tmp; // типы возвращаемого значения и параметров совпадают
}
Параметры шаблона и область видимости
Параметры шаблона следуют обычным правилам области видимости. Имя параметра шаблона применимо сразу после его объявления и до конца объявления или определения шаблона. Подобно любым другим именам, параметр шаблона скрывает любые объявления имен во внешней области видимости. Однако, в отличие от большинства других контекстов, имя, используемое как параметр шаблона, не может быть повторно использовано в пределах шаблона:
typedef double А;
template <typename A, typename В> void f(А а, В b) {
A tmp = а; // tmp имеет тип параметра шаблона А, а не double
double В; // ошибка: повторное объявление параметра шаблона В
}
Согласно обычным правилам сокрытия имен, определение typedef типа А скрывается определением параметра типа по имени А. Таким образом, переменная tmp не будет иметь тип double; она будет иметь любой тип, который будет передан параметру шаблона А при использовании шаблона. Поскольку нельзя многократно использовать имена параметров шаблона, объявление переменной по имени B ошибочно.
Поскольку имя параметра не может быть использовано многократно, в каждом списке параметров шаблона имя параметра шаблона может присутствовать только однажды:
// ошибка: повторение имени V в параметрах шаблона недопустимо
template <typename V, typename V> // ...
Объявления шаблона
Объявление шаблона должно включить параметры шаблона:
// объявляет, но не определяет compare и Blob
template <typename Т> int compare(const T&, const T&);
template <typename T> class Blob;
Подобно параметрам функций, имена параметров шаблона не должны совпадать с таковыми в объявлениях и определениях того же шаблона:
// все три случая использования calc
// относятся к тому же шаблону функции
template <typename Т> Т calc(const Т&, const Т&); // объявление
template <typename U> U calc(const U&, const U&); // объявление
// определение шаблона
template <typename Type>
Type calc(const Type& a, const Type& b) { /* ... */ }
Конечно, у каждого объявления и определения шаблона должно быть то же количество и вид (т.е. тип или значение) параметров.
Использование членов типа
Помните, как в разделах 7.4 и 7.6 использовался оператор области видимости (::) для обращения к статическим членам и членам типа. В обычном коде (не шаблона) у компилятора есть доступ к определению класса. В результате он знает, является ли имя, к которому обращаются через оператор области видимости, типом или статическим членом. Например, в коде string::size_type, компилятор имеет определение класса string и может узнать, что size_type — это тип.
С учетом того, что Т является параметром типа шаблона, когда компилятор встретит такой код, как T::mem, он не будет знать до времени создания экземпляра, является ли mem типом или статической переменной-членом. Но чтобы обработать шаблон, компилятор должен знать, представляет ли имя тип. Например, если T является именем параметра типа, то как компилятор воспримет следующий код:
T::size_type * p;
Он должен знать, определяется ли переменная по имени p или происходит умножение статической переменной-члена по имени size_type на переменную по имени p.
По умолчанию язык подразумевает, что имя, к которому обращаются через оператор области видимости, не является типом. В результате, если необходимо использовать тип-член параметра типа шаблона, следует явно указать компилятору, что имя является типом. Для этого используется ключевое слово typename:
template <typename Т>
typename Т::value_type top(const T& с) {
if (!c.empty())
return c.back();
else
return typename T::value_type();
}
Функция top() ожидает контейнер в качестве аргумента, она использует ключевое слово typename для определения своего типа возвращаемого значения и создает инициализированный по умолчанию элемент (см. раздел 7.5.3), чтобы возвратить его, если у контейнера с нет никаких элементов.
Аргументы шаблона по умолчанию
В качестве примера перепишем функцию сравнения, использующую по умолчанию библиотечный шаблонный объект функции less (см. раздел 14.8.2):
// compare() имеет аргумент шаблона по умолчанию, less<T>
// и заданный по умолчанию аргумент функции, F()
template <typename Т, typename F = less<T>>
int compare(const T &v1, const T &v2, F f = F()) {
if (f(v1, v2)) return -1;
if (f(v2, v1)) return 1;
return 0;
}
Здесь в шаблон добавлен второй параметр типа, F, представляющий тип вызываемого объекта (см. раздел 10.3.2), и определен новый параметр функции, f, который будет связан с вызываемым объектом.
Предоставлено также значение по умолчанию для этого параметра шаблона и соответствующего ему параметра функции. Аргумент шаблона по умолчанию определяет, что функция compare() будет использовать библиотечный класс less объекта функции, экземпляр которого создается с тем же параметром типа, что и функция compare(). Заданный по умолчанию аргумент функции указывает, что параметр f будет инициализирован по умолчанию объектом типа F.
Когда пользователи вызывают эту версию функции compare(), они могут предоставить собственный оператор сравнения, но не обязаны делать это:
bool i = compare(0, 42); // использует less; i равно -1
// результат зависит от isbn в item1 и item2
Sales_data item1(cin), item2(cin);
bool j = compare(item1, item2, compareIsbn);
Первый вызов использует заданный по умолчанию аргумент функции, которым является объект типа less<T>. В этом вызове Т имеет тип int, поэтому у объекта будет тип less<int>. Этот экземпляр функции compare() будет использовать для сравнения тип less<int>.
Во втором вызове передается функция compareIsbn() (см. раздел 11.2.2) и два объекта типа Sales_data. Когда функция compare() вызывается с тремя аргументами, типом третьего аргумента должен быть вызываемый объект, возвращающий тип, приводимый к типу bool и получающий аргументы типа, совместимого с типами первых двух аргументов. Как обычно, типы параметров шаблона выводятся из соответствующих им аргументов функции. В этом вызове тип T выводится как тип Sales_data, а тип F — как тип compareIsbn().
Как и с аргументами функций по умолчанию, у параметра шаблона может быть аргумент по умолчанию, только если у всех параметров справа от него также есть аргументы по умолчанию.
Аргументы по умолчанию шаблона и шаблоны класса
Всякий раз, когда используется шаблон класса, за именем шаблона всегда должны следовать угловые скобки. Скобки означают, что класс будет создан как экземпляр шаблона. В частности, если шаблон класса предоставляет аргументы по умолчанию для всех своих параметров и следует использовать именно их, то после имени шаблона следует поместить пустую пару угловых скобок:
template <class Т = int> class Numbers { // по умолчанию Т - это int
public:
Numbers(Т v = 0): val(v) { } // различные операции с числами
private:
Т val;
};
Numbers<long double> lots_of_precision;
Numbers<> average_precision; // пустые <> означают тип по умолчанию
Здесь создаются два экземпляра шаблона Numbers: версия average_ precision — экземпляр Numbers с заменой параметра Т типом int; версия lots_of_precision — экземпляр Numbers с заменой параметра Т типом long double.
Упражнения раздела 16.1.3
Упражнение 16.17. Каковы (если есть) различия между параметром типа, объявленным с ключевым словом typename и ключевым словом class? Когда должно использоваться ключевое слово typename?
Упражнение 16.18. Объясните каждое из следующих объявлений шаблона функции и укажите, допустимы ли они. Исправьте все найденные ошибки.
(a) template <typename Т, U, typename V> void f1(T, U, V);
(b) template <typename T> T f2(int &T);
(c) inline template <typename T> T foo(T, unsigned int*);
(d) template <typename T> f4(T, T);
(e) typedef char Ctype;
template <typename Ctype> Ctype f5(Ctype a);
Упражнение 16.19. Напишите функцию, получающую ссылку на контейнер и выводящую его элементы. Используйте переменную size_type и функцию-член size() контейнера для контроля цикла, вывода элементов.
Упражнение 16.20. Перепишите функцию из предыдущего упражнения так, чтобы использовать для контроля цикла итераторы, возвращаемые функциями begin() и end().