Spec-Zone.ru › GCC 10

Next: Временные объекты могут исчезнуть раньше, чем ожидается, Previous: Объявляйте и определять статические члены, Up: Распространённые недоразумения с GNU C++ [Оглавление][Индекс]

14.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++ просто использует термин «зависимое» для имён, которые зависят от типа или значения параметров шаблона. Этот более короткий термин также будет использоваться в остальной части этого раздела.

Next: Временные объекты могут исчезнуть раньше, чем ожидается, Previous: Объявляйте и определять статические члены, Up: Распространённые недоразумения с GNU C++ [Оглавление][Индекс]

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

Spec-Zone.ru

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