Spec-Zone.ru › GCC 7

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

Spec-Zone.ru

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