template<typename T>
class Base
{
protected:
Base() {}
T& get() { return t; }
T t;
};
template<typename T>
class Derived : public Base<T>
{
public:
Base<T>::get; // Line A
Base<T>::t; // Line B
void foo() { t = 4; get(); }
};
int main() { return 0; }
如果我注释掉A行和B行,这段代码在Visual Studio 2008下编译得很好。但是当我在GCC 4.1下用A和B行编译时,我得到了这些错误:
在成员函数'void Derived :: foo()'中:
错误:在此范围内未声明't' 错误:“get”没有依赖于模板参数的参数,因此“get”的声明必须可用
为什么一个编译器需要A行和B行而另一个编译器不需要?有没有办法简化这个?换句话说,如果派生类使用基类中的20个东西,我必须为从Base派生的每个类放置20行声明!有没有办法解决这个问题?
答案 0 :(得分:15)
GCC在这种情况下是正确的,并且Visual Studio错误地接受了格式错误的程序。请查看GCC手册中Name lookup部分。意译:
[T]他对[
get()
]的调用不依赖于模板参数(没有依赖于类型T的参数,也没有指定调用应该在[模板]中 - ]依赖的上下文)。 因此,必须提供此类函数的全局声明,因为基类中的函数在实例化时才可见。
您可以通过以下三种方式解决此问题:
Base<T>::get()
this->get()
(还有第四种方式,如果你想屈服于黑暗面:
使用
-fpermissive
标志也会让编译器接受代码,方法是标记所有函数调用,在定义模板时没有声明可见,以便以后在实例化时查找,就像它是一个从属电话。我们不建议使用-fpermissive
来解决无效代码,它也只会捕获调用基类函数的情况,而不是使用基类中的变量的情况(如上例所示)。
但我建议不要这样做,因为手册中提到的原因,以及你的代码仍然是无效的C ++的原因。)
答案 1 :(得分:5)
问题不在于gcc,而在于Visual Studio,它接受的代码不符合C ++标准。
在这个网站上已经回答过,所以我会简短。
标准要求对模板进行两次评估:
template <class T> struct Foo { void bar(); };
Foo<int> myFoo;
第一次,所有非依赖名称都可以从上下文中扣除:
由于C ++语法含糊不清,因此有必要在此阶段帮助解析器,并适当地使用template
和typename
关键字来手动消除歧义。
不幸的是,Visual Studio不符合要求,只实现第二次评估(在实例化时)。懒惰的优势在于,如果没有额外的template
和typename
关键字,您可以逃脱,缺点是您的代码格式错误且无法移植......
现在有趣的部分:
void foo(int) { std::cout << "int" << std::endl; }
template <class T> void tfoo(T i) { foo(i); }
void foo(double) { std::cout << "double" << std::endl; }
int main(int argc, char* argv[])
{
double myDouble = 0.0;
tfoo(myDouble);
return 0;
}
使用gcc编译,输出int
。
使用Visual Studio编译,输出double
。
这个问题?任何重复使用你在VS中的模板代码中执行的相同符号的人,如果他们的符号出现在包含模板代码和他们实际使用模板代码的那一刻之间,那么他们可能会踩到你的实现一团糟......不是吗好笑:/?
现在,为您的代码:
template<typename T>
class Derived : public Base<T>
{
public:
void foo() { this->t = 4; this->get(); }
};
this
表示以下名称是依赖名称,即它取决于T
(当符号单独出现时不明显)。因此,编译器将等待instanciation,并查看您实例化模板Base<T>
的特定类型是否包含这些方法。这不是强制性的,因为我可以完全专注Base
:
// It's non-sensical to instanciate a void value,
template <>
class Base<void> {};
因此Derived<void>
不应编译;)