GCC 问题:使用依赖于模板参数的基类成员

Posted

技术标签:

【中文标题】GCC 问题:使用依赖于模板参数的基类成员【英文标题】:GCC issue: using a member of a base class that depends on a template argument 【发布时间】:2008-08-14 17:39:46 【问题描述】:

以下代码不能用 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++ 官方规范中是否有某些内容,还是只是一个怪癖?

【问题讨论】:

见In a templated derived class, why do I need to qualify base class member names with “this->” inside a member function? 发生这种情况是因为两阶段名称查找(并非所有编译器都默认使用)。这个问题有4个解决方案:1)使用前缀A&lt;T&gt;::foo,2)使用前缀this-&gt;foo,3)添加a statement using A&lt;T&gt;::foo, 4) 使用启用许可模式的全局编译器开关。这些解决方案的优缺点在***.com/questions/50321788/… 中进行了描述 【参考方案1】:

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

编译B&lt;T&gt;时的问题是它的基类A&lt;T&gt;在编译器中是未知的,是一个模板类,所以编译器无法知道基类中的任何成员。

早期版本通过实际解析基本模板类进行了一些推断,但 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 manual 中的更多信息。

【讨论】:

一方面,这是有道理的。但另一方面,感觉真的很蹩脚。在模板被实例化之前,编译器需要知道foo 指的是什么,此时,它应该能够识别A 中的foo 成员。 C++ 有太多这些奇怪的极端情况。 是的,这是完全不能接受的……在实例化之前不能知道吗?然后像往常一样在模板中等待实例化......这就是它的精神,不是吗?真是一团糟……【参考方案2】:

哇。 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 
; 

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

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的成员A::BA::aA::Y不影响Y&lt;A&gt;中名称的绑定。 ]

【讨论】:

【参考方案3】:

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

【讨论】:

【参考方案4】:

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

template<>
class A<int> ;

B<int> x; 
x.bar();//this will fail because there is no member foo in A<int>

【讨论】:

【参考方案5】:

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

【讨论】:

以上是关于GCC 问题:使用依赖于模板参数的基类成员的主要内容,如果未能解决你的问题,请参考以下文章

派生类中隐藏的基类模板成员函数,尽管参数列表不同

带有接受派生类参数的基类参数的成员函数指针

无法从派生类构造函数参数访问受保护的基类成员[重复]

如何初始化作为另一个类的成员变量的基类对象?

在派生类中专门化模板成员

可以通过[重复]在派生类中初始化受保护的基类成员