我将代码从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行:错误:'打印'不是'Baz'的成员
谁是对的?如果gcc是对的,为什么?
答案 0 :(得分:8)
gcc是对的。模板分为两个阶段:一次解析时,一次实例化。
在分析时,会检查不依赖于模板参数的任何内容,因此在这种情况下会查找Baz::Print
,并且发现没有声明,所以这是一个错误。
如果调用是T().Print()
或依赖类型T
的其他内容,那么查找将延迟到实例化时间(或者至少部分延迟 - 见下文。)
T::Print
)之外,Baz :: Print等合格的名称总是在定义时查找,尽管重载决议被推迟到实例化时间。这意味着在声明模板后,您无法添加限定名称的重载集。
如果任何函数参数依赖于模板参数,则会在实例化时查找仅限Print
的非限定名称。因此Print(T())
会在实例化时查找,而Baz::Print(T())
则不会。值得注意的是,实例化时间查找仅限于在声明点可见的名称和通过ADL找到的名称,因此对于Foo<int>
,即使普通Print(T())
也找不到Baz::Print
如果它是在模板之后声明的(如示例所示)。
要使示例代码有效,请在定义Foo<T>::Bar
后定义Print
,或在Print
定义之前定义Foo
。
答案 1 :(得分:4)
gcc是对的。这是因为Baz
是一个名称空间,名称空间从上到下进行了解析,因此Baz::Print
内部Foo
的声明不可见(因为它在它下面)。
当实例化模板时,只考虑模板定义中可见的名称,而不考虑Koenig查找(在您的情况下不会改变任何内容)。
如果Baz
是一个结构或类,你的代码就可以工作,因为它们分两个阶段进行解析(第一个声明,然后是主体),所以在结构或类中声明的任何内容都是可见的。成员函数,它们在源文件中的顺序。
您可以在Baz::Print
之前声明Foo
。
引用标准:
模板定义中使用的非依赖名称可以使用通常的名称查找找到并绑定在 他们被使用了。
在解析依赖名称时,会考虑以下来源的名称:
(结束语录)
当Print
非独立时(现在如此),由于在声明之前(在模板定义上下文中)查找它,因此无法找到它。如果它是依赖的,它将不是第一种情况(与非依赖时相同),并且Baz
不以任何方式与int(模板参数)相关联,因此不会根据第二种情况。