Я нашел странную проблему при портировании моего кода от 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;
}
Мое понимание - то, что это должно скомпилировать OK, поскольку класс не должен быть скомпилирован, пока шаблон не инстанцируют (который является после того, как Печать () определяется). Однако gcc сообщает о следующем:
t.cpp: В функции членства 'освобождают Baz:: Нечто:: Панель ()': Строка 8: ошибка: 'Печать' не является членом 'Baz'
Кто прав? И если gcc является правильным, почему?
gcc прав. Это потому, что Baz
- пространство имен, а пространства имен разбираются сверху вниз, поэтому объявление Baz::Print
не видно изнутри Foo
(поскольку оно находится под ним).
При инстанцировании шаблона учитываются только имена, видимые из определения шаблона, без учета поиска Кёнига (что ничего не изменит в вашем случае).
Если бы Baz
был структурой или классом, ваш код работал бы, поскольку они разбираются в два этапа (сначала объявления, затем тела), поэтому все, что объявлено в структуре или классе, видно внутри, например, функций-членов, независимо от их порядка в исходном файле.
Вы можете заставить это работать, объявив Baz::Print
перед Foo
.
Цитирую стандарт:
Независимые имена, используемые в определении шаблона, находятся с помощью обычного поиска имен и связываются в том месте, где они используются. в месте их использования.
При разрешении зависимых имен рассматриваются имена из следующих источников:
(конец цитаты)
Когда Print
не является зависимым (как сейчас), он не будет найден, так как его ищут до его объявления (в контексте определения шаблона). Если бы он был зависимым, то не было бы первого случая (так же, как и в случае, когда он не является зависимым), а Baz
никак не связан с int (параметром шаблона), поэтому он не будет искаться и во втором случае.
gcc прав. Шаблоны компилируются в два этапа: один раз при их анализе и один раз при их создании.
Во время синтаксического анализа проверяется все, что НЕ зависит от параметров шаблона, поэтому в этом случае выполняется поиск Baz :: Print
и обнаруживается, что он не был объявлен, так что это ошибка .
Если бы звонок был T ().Print ()
или что-то еще, зависящее от типа T
, тогда поиск будет отложен до времени создания экземпляра (или, по крайней мере, частично отложен - см. Ниже).
Полные имена, такие как Baz: : Print всегда просматривается во время определения, если сама квалификация не зависит от параметра шаблона (например, T :: Print
), хотя разрешение перегрузки откладывается до времени создания экземпляра. Это означает, что вы не можете добавить к набору перегрузки полные имена после объявления шаблона.
Неквалифицированные имена, такие как просто Print
, просматриваются во время создания экземпляра, если какой-либо из аргументов функции зависит от параметра шаблона. Таким образом, Print (T ())
будет просматриваться во время создания экземпляра, а Baz :: Print (T ())
- нет. Стоит отметить, что поиск во время создания экземпляра ограничен теми именами, которые видны в точке объявления и теми, которые были найдены через ADL, поэтому для Foo
даже простой Print (T () )
не должен находить Baz :: Print
, если он был объявлен после шаблона (как в примере).
Чтобы пример кода работал, либо определите Foo
после определения Print
, либо выполните прямое объявление Print
перед определение Foo
.