Spec-Zone.ru › GCC 5

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) неправильно интерпретируют эти примеры и принимают указанный выше код без ошибки. Эти компиляторы не реализуют двухэтапный поиск имен корректно.

Примечания
(5)

Стандарт C++ использует термин «зависимое» для имен, которые зависят от типа или значения параметров шаблона. Этот более короткий термин также будет использоваться в остальной части этого раздела.

Далее: Временные объекты, Предыдущее: Статические определения, Наверх: Непонимание C++ [Содержание][Индекс]

© Free Software Foundation
Licensed under the GNU Free Documentation License, Version 1.3.
https://gcc.gnu.org/onlinedocs/gcc-5.5.0/gcc/Name-lookup.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API