При обсуждении разрешения имен в шаблонах функций (см. раздел 10.9) мы уже говорили о том, что этот процесс выполняется в два шага. Так же разрешаются имена и в определениях шаблонов классов и их членов. Каждый шаг относится к разным видам имен: первый - к тем, которые имеют один и тот же смысл во всех экземплярах шаблона, а второй - к тем, которые потенциально могут иметь разный смысл в разных экземплярах. Рассмотрим несколько примеров, где используется функция-член remove() шаблона класса Queue:
// Queue.h:
#include iostream
#include cstdlib
// определение класса Queue
template class Type
Type QueueType::remove() {
if ( is_empty() ) {
cerr "remove() вызвана для пустой очереди\n";
exit(-1);
}
QueueItemType *pt = front;
front = front-next;
Type retval = pt-item;
delete pt;
cout "удалено значение: ";
cout retval endl;
return retval;
}
В выражении
cout retval endl;
переменная retval имеет тип Type, и ее фактический тип неизвестен до конкретизации функции-члена remove(). То, какой оператор operator() будет выбран, зависит от фактического типа retval, подставленного вместо Type. При разных конкретизациях remove() могут вызываться разные operatorОднако для вызова функции exit() ситуация иная. Ее фактическим аргументом является литерал, значение которого одинаково при всех конкретизациях remove(). Поскольку при обращении к функции не используются аргументы, типы которых зависят от параметра шаблона Type, гарантируется, что всегда будет вызываться exit(), объявленная в заголовочном файле cstdlib. По той же причине в выражении
cout "удалено значение: ";
всегда вызывается глобальный оператор
ostream& operator& & ( ostream &, const char * );
Аргумент "удалено значение: " - это C-строка символов, и ее тип не зависит от параметра шаблона Type. Поэтому в любом конкретизированном экземпляре remove()употребление operator() имеет одинаковый смысл. Один и тот же смысл во всех конкретизациях шаблона имеют те конструкции, которые не зависят от параметров шаблона.
* Таким образом, два шага разрешения имени в определениях шаблонов классов или их членов состоят в следующем: Имена, не зависящие от параметров шаблона, разрешаются во время его определения.
* Имена, зависящие от параметров шаблона, разрешаются во время его конкретизации.
Такой подход удовлетворяет требованиям как разработчика класса, так и его пользователя. Например, разработчикам необходимо управлять процессом разрешения имен. Если шаблон класса входит в состав библиотеки, в которой определены также другие шаблоны и функции, то желательно, чтобы при конкретизации шаблона класса и его членов по возможности применялись именно библиотечные компоненты. Это гарантирует первый шаг разрешения имени. Если использованное в определении шаблона имя не зависит от параметров шаблона, то оно разрешается в результате просмотра всех объявлений, видимых в заголовочном файле, включенном перед определением шаблона.
Разработчик класса должен позаботиться о том, чтобы были видимы объявления всех не зависящих от параметров шаблона имен, употребленных в его определении. Если объявление такого имени не найдено, то определение шаблона считается ошибочным. Если бы перед определением функции-члена remove() в шаблоне класса Queue не были включены файлы iostream и cstdlib, то в выражении
cout "удалено значение: ";
и при компиляции вызова функции exit() были бы обнаружены ошибки.
Второй шаг разрешения имени необходим, если поиск производится среди функций и операторов, зависящих от типа, которым конкретизирован шаблон. Например, если шаблон класса Queue конкретизируется типом класса LongDouble (см. раздел 16.9), то желательно, чтобы внутри функции-члена remove()в следующем выражении
cout retval endl;
вызывался оператор operator (), ассоциированный с классом LongDouble:
#include "Queue.h"
#include "ldouble.h"
// содержит:
// class LongDouble { ... };
// ostream& operator&&( ostream &, const LongDouble & );
int main() {
// конкретизация QueueLongDouble
QueueLongDouble *qld = new QueueLongDouble;
// конкретизация QueueLongDouble::remove()
// вызывает оператор вывода для LongDouble
qld-remove();
// ...
}