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