11.7.2 Поиск имен, шаблоны и доступ к членам базовых классов
Стандарт C++ предписывает, что все имена, которые не зависят от параметров шаблона, связываются с их текущими определениями при разборе шаблонной функции или класса.1 Только зависимые имена ищутся в момент инстанцирования. Например, рассмотрим
void foo(double);
struct A {
template <typename T>
void f () {
foo (1); // 1
int i = N; // 2
T t;
t.bar(); // 3
foo (t); // 4
}
static const int N;
}; Здесь имена foo и N появляются в контексте, не зависящем от типа T. Таким образом, компилятор потребует, чтобы они были определены в контексте использования в шаблоне, а не только до момента инстанцирования, и здесь будет использоваться ::foo(double) и A::N соответственно. В частности, он преобразует целое значение в double при передаче в ::foo(double).
Напротив, bar и вызов foo в четвёртой помеченной строке используются в контексте, зависящем от типа T, поэтому они ищутся только в момент инстанцирования, и вы можете предоставить объявления после объявления шаблона, но перед его инстанцированием. В частности, если вы инстанцируете A::f<int>, последняя строка вызовет перегруженный ::foo(int), если таковой был предоставлен, даже после объявления struct A.
Это различие между поиском зависимых и независимых имён называется двухэтапным (или зависимым) поиском имён. G++ реализует его начиная с версии 3.4.
Двухэтапный поиск имён иногда приводит к ситуациям с поведением, отличным от поведения кода без шаблонов. Наиболее распространённой, вероятно, является следующая:
template <typename T> struct Base {
int i;
};
template <typename T> struct Derived : public Base<T> {
int get_i() { return i; }
}; В get_i(), i не используется в зависимом контексте, поэтому компилятор будет искать имя, объявленное в области охватывающего пространства имён (в данном случае глобального пространства имён). Он не будет искать в базовом классе, так как это зависимый поиск, и вы можете объявлять специализации Base даже после объявления Derived, поэтому компилятор не может точно знать, к чему будет относиться i. Если глобальной переменной i не существует, то вы получите сообщение об ошибке.
Чтобы сделать ясным, что вам нужен член базового класса, необходимо отложить поиск до момента инстанцирования, когда базовый класс становится известным. Для этого необходимо обратиться к i в зависимом контексте, используя либо this->i (помните, что this имеет тип Derived<T>*, поэтому очевидно зависит), либо используя Base<T>::i. В качестве альтернативы, Base<T>::i может быть включено в область видимости с помощью объявления using.
Ещё один, аналогичный пример, связан с вызовом членов функций базового класса:
template <typename T> struct Base {
int f();
};
template <typename T> struct Derived : Base<T> {
int g() { return f(); };
}; Опять же, вызов f() не зависит от аргументов шаблона (нет аргументов, зависящих от типа T, и также не указано, что вызов должен быть в зависимом контексте). Таким образом, необходимо наличие глобального объявления такой функции, так как функция в базовом классе не видна до момента инстанцирования. Вследствие этого компилятор сгенерирует следующее сообщение об ошибке:
x.cc: In member function `int Derived<T>::g()': x.cc:6: error: there are no arguments to `f' that depend on a template parameter, so a declaration of `f' must be available x.cc:6: error: (if you use `-fpermissive', G++ will accept your code, but allowing the use of an undeclared name is deprecated)
Чтобы сделать код валидным, необходимо либо использовать this->f(), либо Base<T>::f(). Использование флага -fpermissive также позволит компилятору принять код, пометив все вызовы функций, для которых объявление не видно на момент определения шаблона, для последующего поиска в момент инстанцирования, как если бы это был зависимый вызов. Не рекомендуется использовать -fpermissive для обхода невалидного кода, а также оно будет находить только случаи вызова функций в базовых классах, а не использования переменных в базовых классах (как в приведённом выше примере).
Обратите внимание, что некоторые компиляторы (включая версии G++ до 3.4) ошибаются в этих примерах и принимают вышеуказанный код без ошибки. Эти компиляторы не реализуют двухэтапный поиск имён правильно.
[1] Стандарт C++ использует термин «зависимый» для имён, которые зависят от типа или значения параметров шаблона. Этот более короткий термин также будет использоваться в остальной части этого раздела.
© Free Software Foundation
Licensed under the GNU Free Documentation License, Version 1.3.
https://gcc.gnu.org/onlinedocs/gcc-4.9.4/gcc/Name-lookup.html