Spec-Zone.ru › GCC 15

15.7.2 Поиск имён, шаблоны и доступ к членам базовых классов

Стандарт C++ предписывает, что все имена, не зависящие от параметров шаблона, связываются с их текущими определениями при разборе шаблонной функции или класса.8 Только поиск зависимых имён выполняется в точке инстанцирования. Например, рассмотрим

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 соответственно. В частности, при передаче значения целого типа в ::foo(double) оно будет преобразовано в 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) неверно обрабатывают эти примеры и принимают приведённый выше код без ошибки. Такие компиляторы неправильно реализуют двухэтапный поиск имён.

Сноски

(8)

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

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

Spec-Zone.ru

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