14.7.2 Поиск имен, шаблоны и доступ к членам базовых классов ¶
Стандарт C++ предписывает, что все имена, не зависящие от параметров шаблона, связываются со своими текущими определениями при разборе шаблонной функции или класса.9 Только зависимые имена ищутся в момент инстанцирования. Например, рассмотрим
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) ошибаются в этих примерах и принимают вышеприведённый код без ошибок. Эти компиляторы не правильно реализуют двухэтапный поиск имён.
Примечания
(9)
Стандарт C++ использует термин «зависимый» для имён, зависящих от типа или значения параметров шаблона. Этот более короткий термин будет также использоваться в остальной части этого раздела.
© Free Software Foundation
Licensed under the GNU Free Documentation License, Version 1.3.
https://gcc.gnu.org/onlinedocs/gcc-11.4.0/gcc/Name-lookup.html