【问题标题】:GCC issue: using a member of a base class that depends on a template argumentGCC 问题:使用依赖于模板参数的基类成员
【发布时间】:2010-09-05 21:36:13
【问题描述】:

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

【问题讨论】:

标签: c++ templates base-class class-members name-lookup


【解决方案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”。除非你告诉编译器它来自哪里,否则在你实例化它之前它根本无法检查模板的有效性。 这就是为什么你必须告诉编译器它来自哪里。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-10-31
          • 1970-01-01
          • 2013-03-30
          • 1970-01-01
          相关资源
          最近更新 更多