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) { /* ... */ }

Конечно, у каждого объявления и определения шаблона должно быть то же количество и вид (т.е. тип или значение) параметров.

По причинам, рассматриваемым в разделе 16.3, объявления всех шаблонов, необходимых данному файлу, обычно располагаются вместе в начале файла перед любым использующим их кодом.

Использование членов типа

Помните, как в разделах 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), чтобы возвратить его, если у контейнера с нет никаких элементов.

Когда необходимо уведомить компилятор о том, что имя представляет тип, следует использовать ключевое слово typename, а не class.

Аргументы шаблона по умолчанию

Аналогично тому, как можно предоставить аргументы по умолчанию для параметров функции (см. раздел 6.5.1), можно предоставить аргументы шаблона по умолчанию (default template argument). По новому стандарту можно предоставлять аргументы по умолчанию и для шаблонов функций, и для шаблонов классов. Прежние версии языка допускали аргументы по умолчанию только для шаблонов класса.

В качестве примера перепишем функцию сравнения, использующую по умолчанию библиотечный шаблонный объект функции 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().

Более 800 000 книг и аудиокниг! 📚

Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением

ПОЛУЧИТЬ ПОДАРОК