37

以下代码不能用 gcc 编译,但可以用 Visual Studio 编译:

template <typename T> class A {
public:
    T foo;
};

template <typename T> class B: public A <T> {
public:
    void bar() { cout << foo << endl; }
};

我得到错误:

test.cpp:在成员函数'void B::bar()'中:

test.cpp:11:错误:“foo”未在此范围内声明

但它应该是!如果我bar改为

void bar() { cout << this->foo << endl; }

然后它编译,但我认为我不必这样做。GCC 在此处遵循的 C++ 官方规范中有什么内容,还是只是一个怪癖?

4

5 回答 5

34

大卫乔伊纳有历史,这就是原因。

编译时的问题B<T>是它的基类A<T>对于编译器是未知的,是一个模板类,所以编译器无法知道基类中的任何成员。

早期版本通过实际解析基本模板类进行了一些推断,但 ISO C++ 声明这种推断可能会导致不应该发生的冲突。

在模板中引用基类成员的解决方案是使用this(像您一样)或专门命名基类:

template <typename T> class A {
public:
    T foo;
};

template <typename T> class B: public A <T> {
public:
    void bar() { cout << A<T>::foo << endl; }
};

gcc 手册中的更多信息。

于 2008-08-14T18:09:55.413 回答
19

哇。C++ 的怪异总是让我感到惊讶。

在模板定义中,非限定名称将不再找到依赖基的成员(如 C++ 标准中的 [temp.dep]/3 所指定)。例如,

template <typename T> struct B {
  int m;
  int n;
  int f ();
  int g ();
};
int n;
int g ();
template <typename T> struct C : B<T> {
  void h ()
  {
    m = 0; // error
    f ();  // error
    n = 0; // ::n is modified
    g ();  // ::g is called
  }
};

您必须使名称依赖,例如通过在它们前面加上 this->。这是 C::h 的更正定义,

template <typename T> void C<T>::h ()
{
  this->m = 0;
  this->f ();
  this->n = 0
  this->g ();
}

作为替代解决方案(不幸的是不向后兼容 GCC 3.3),您可以使用 using 声明而不是 this->:

template <typename T> struct C : B<T> {
  using B<T>::m;
  using B<T>::f;
  using B<T>::n;
  using B<T>::g;
  void h ()
  {
    m = 0;
    f ();
    n = 0;
    g ();
  }
};

这简直是​​各种疯狂。谢谢,大卫。

这是他们所指的标准 [ISO/IEC 14882:2003] 的“temp.dep/3”部分:

在类模板或类模板成员的定义中,如果类模板的基类依赖于模板参数,则在类定义点的非限定名称查找期间不会检查基类范围模板或成员,或在类模板或成员的实例化期间。[例子:

typedef double A; 
template<class T> class B { 
    typedef int A; 
}; 
template<class T> struct X : B<T> { 
    A a; // a has typedouble 
}; 

A定义中的类型名称X<T>绑定到全局命名空间范围中定义的 typedef 名称,而不是基类中定义的 typedef 名称B<T>。] [例子:

struct A { 
    struct B { /* ... */ }; 
    int a; 
    int Y; 
}; 
int a; 
template<class T> struct Y : T { 
    struct B { /* ... */ }; 
    B b; //The B defined in Y 
    void f(int i) { a = i; } // ::a 
    Y* p; // Y<T> 
}; 
Y<A> ya; 

模板参数的成员A::BA::a和不影响 中名称的绑定。]A::YAY<A>

于 2008-08-14T18:05:09.510 回答
12

这在gcc-3.4中发生了变化。C++ 解析器在那个版本中变得更加严格——按照规范,但对于拥有遗留或多平台代码库的人来说仍然有点烦人。

于 2008-08-14T17:50:24.750 回答
8

C++ 在这里不能假设任何东西的主要原因是基本模板可以在以后专门用于一种类型。继续原来的例子:

template<>
class A<int> {};

B<int> x; 
x.bar();//this will fail because there is no member foo in A<int>
于 2008-08-14T21:06:41.360 回答
3

VC 没有实现两阶段查找,而 GCC 有。因此 GCC 在实例化模板之前对其进行解析,从而发现比 VC 更多的错误。在您的示例中, foo 是一个从属名称,因为它取决于“T”。除非你告诉编译器它来自哪里,否则在你实例化它之前它根本无法检查模板的有效性。这就是为什么你必须告诉编译器它来自哪里。

于 2008-10-17T13:21:45.927 回答