Разве функции-члены шаблона не скомпилированы при создании экземпляра? - PullRequest
5 голосов
/ 15 июля 2010

Я обнаружил странную проблему при переносе моего кода из Visual Studio в gcc. Следующий код прекрасно компилируется в Visual Studio, но приводит к ошибке в gcc.

namespace Baz
{
   template <class T>
   class Foo
   {
   public:
      void Bar()
      {
         Baz::Print();
      }
   }; 

   void Print() { std::cout << "Hello, world!" << std::endl; }
}

int main()
{
   Baz::Foo<int> foo;
   foo.Bar();

   return 0;
}

Насколько я понимаю, это должно скомпилироваться нормально, так как класс не должен компилироваться до тех пор, пока не будет создан экземпляр шаблона (то есть после определения Print ()). Однако gcc сообщает следующее:

t.cpp: В функции-члене 'void Baz :: Foo :: Bar ()': Строка 8: ошибка: «Print» не является членом «Baz»

Кто прав? И если gcc прав, почему?

Ответы [ 2 ]

8 голосов
/ 15 июля 2010

GCC правильно.Шаблоны компилируются в два этапа: один раз, когда они анализируются, и один раз, когда они создаются.

Во время анализа проверяется все, что НЕ зависит от параметров шаблона, поэтому в этом случае Baz::Printпосмотрел вверх и обнаружил, что он не был объявлен, так что это ошибка.

Если вызов был T().Print() или что-то еще, что зависело от типа T, тогда поиск будет отложен до момента его созданиявремя (или хотя бы частично задержанное - см. ниже.)

Квалифицированные имена, такие как Baz :: Print, всегда ищутся во время определения, если только сама квалификация не зависит от параметра шаблона (например, T::Print), хотяразрешение перегрузки откладывается на время создания экземпляра.Это означает, что вы не можете добавить в набор перегрузки квалифицированные имена после того, как шаблон был объявлен.

Неквалифицированные имена, такие как просто Print, ищутся во время создания экземпляра, если какой-либо из аргументов функции зависит от шаблонапараметр.Таким образом, Print(T()) будет проверяться во время создания экземпляра, а Baz::Print(T()) - нет.Стоит отметить, что поиск во время создания экземпляра ограничен теми именами, которые видны в точке объявления, и теми, которые найдены через ADL, поэтому для Foo<int> даже простой Print(T()) не должен найти Baz::Print, если он был объявлен послеtemplate (как в примере).

Чтобы сделать пример кода работающим, либо определите Foo<T>::Bar после определения Print, либо опишите вперед Print перед определением Foo.

4 голосов
/ 15 июля 2010

GCC правильно.Это потому, что Baz является пространством имен, а пространства имен анализируются сверху вниз, поэтому объявление Baz::Print не видно изнутри Foo (так как оно находится под ним).

Когда шаблонсоздаются только имена, видимые из определения шаблона, не считая поиска Кенига (который ничего не изменит в вашем случае).

Если бы Baz была структурой или классом, ваш код работал бы, так какони анализируются в два этапа (сначала объявления, затем тела), поэтому все, что объявлено в структуре или классе, видно внутри, например.функции-члены, относящиеся к их порядку в исходном файле.

Вы можете заставить его работать, объявив Baz::Print перед Foo.

Цитируя стандарт:

14.6.3 Независимые имена

Независимые имена, используемые в определении шаблона, находятся с использованием обычного поиска имен и привязываются в той точке, в которой они используются.

14.6.4 Разрешение зависимых имен

При разрешении зависимых имен учитываются имена из следующих источников:

  • Объявления, видимые в точке определения шаблона.
  • Объявления из связанных пространств именс типами аргументов функции как из контекста экземпляра (14.6.4.1), так и из контекста определения.

(заключительная кавычка)

Когда Print не зависит (какэто сейчас), он не будет найден, так как он ищется до его объявления (в контексте определения шаблона).Если бы он был зависимым, это был бы не первый случай (такой же, как и независимый), и Baz никак не связан с int (параметром шаблона), поэтому поиск по второму случаю не производился бы.либо.

...