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-8.5.0/gcc/Name-lookup.html