为什么当VS没有时,GCC在模板中需要额外的声明?

Kyl*_*yle 16 c++ gcc templates

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; }
Run Code Online (Sandbox Code Playgroud)

如果我注释掉A行和B行,这段代码在Visual Studio 2008下编译得很好.但是当我在GCC 4.1下用A和B行编译时,我得到了以下错误:

在成员函数'void Derived :: foo()'中:
错误:'t'未在此范围内声明
错误:没有依赖于模板参数的'get'参数,因此'get'的声明必须是可得到

为什么一个编译器需要A行和B行而另一个编译器不需要?有没有办法简化这个?换句话说,如果派生类使用基类中的20个东西,我必须为从Base派生的每个类放置20行声明!有没有办法解决这个问题?

Tho*_*mas 15

在这种情况下,GCC是正确的,并且Visual Studio错误地接受了格式错误的程序.请查看GCC手册中有关名称查找的部分.复述:

[T]他对[ get()]的调用不依赖于模板参数(没有依赖于类型T的参数,也没有指定调用应该在[template-]依赖的上下文中).因此,必须提供此类函数的全局声明,因为基类中的函数在实例化时间之前是不可见的.

您可以通过以下三种方式解决此问题:

  • 您已经使用的声明.
  • Base<T>::get()
  • this->get()

(还有第四种方法,如果你想屈服于黑暗面:

使用该-fpermissive标志还将允许编译器接受代码,方法是标记所有函数调用,在定义模板时没有声明可见,以便稍后在实例化时查找,就像它是依赖调用一样.我们不建议使用-fpermissive解决无效代码,它也只会捕获调用基类函数的情况,而不是使用基类中的变量的情况(如上例所示).

但是我建议不要这样做,因为手册中提到的原因,以及你的代码仍然是无效的C++的原因.)


Mat*_* M. 6

问题不在于gcc,而在于Visual Studio,它接受的代码不符合C++标准.

它已经在这个网站上得到了回答,所以我会简短一些.

该标准要求模板评估两次:

  • 一旦达到定义: template <class T> struct Foo { void bar(); };
  • 一旦到了实例: Foo<int> myFoo;

第一次,所有非依赖名称都可以从上下文中扣除:

  • 如果忘记标点符号,编译器会抛出,指的是未知类型/方法/属性
  • 编译器将为此时涉及的函数选择重载

由于C++语法不明确,因此有必要在此阶段帮助解析器,并使用templatetypename关键字相应地手动消除歧义.

不幸的是,Visual Studio不符合要求,只实现第二次评估(在实例化时).懒惰的优点是你可以在没有这些额外templatetypename关键字的情况下逃脱,缺点是你的代码格式不正确而且不便携......

现在是有趣的部分:

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;
}
Run Code Online (Sandbox Code Playgroud)

用gcc编译,输出int.

使用Visual Studio编译,输出double.

问题 ?任何重复使用你在VS中的模板代码中执行的相同符号的人,如果他们的符号出现在包含模板代码和他们实际使用模板代码的那一刻之间,可能会让你的实施陷入困境......不是吗好笑:/?

现在,为您的代码:

template<typename T>
class Derived : public Base<T>
{
public:
  void foo() { this->t = 4; this->get(); }
};
Run Code Online (Sandbox Code Playgroud)

this指示以下名字是一个从属名称,即它取决于T(当符号单独出现这不是很明显).因此,编译器将等待instanciation,并查看您实例化模板的特定类型是否Base<T>包含这些方法.这不是强制性的,因为我可以完全专注Base:

// It's non-sensical to instanciate a void value,
template <>
class Base<void> {};
Run Code Online (Sandbox Code Playgroud)

因此Derived<void>不应该编译;)