海湾合作委员会问题:使用依赖于模板参数的基类成员
-
08-06-2019 - |
题
以下代码不能使用 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; }
};
我收到错误:
测试.cpp:在成员函数“void B::bar()”中:
测试.cpp:11:错误:'foo' 未在此范围内声明
但应该如此!如果我改变 bar
到
void bar() { cout << this->foo << endl; }
然后它 做 编译,但我认为我不必这样做。GCC 是否遵循 C++ 官方规范中的某些内容,或者这只是一个怪癖?
解决方案
这改变了 海湾合作委员会-3.4. 。C++ 解析器在该版本中变得更加严格——根据规范,但对于拥有遗留或多平台代码库的人来说仍然有点烦人。
其他提示
大卫·乔伊纳有过这样的历史,原因如下。
编译时的问题 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; }
};
更多信息请参见 海湾合作委员会手册.
哇。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::B
,A::a
, , 和A::Y
模板参数的A
不影响名称的绑定Y<A>
. ]
C++ 在这里不能假设任何事情的主要原因是基础模板可以稍后专门用于某种类型。继续原来的例子:
template<>
class A<int> {};
B<int> x;
x.bar();//this will fail because there is no member foo in A<int>
VC 没有实现两阶段查找,而 GCC 实现了。因此,GCC 在实例化模板之前对其进行解析,因此比 VC 发现更多错误。在您的示例中, foo 是一个从属名称,因为它依赖于“T”。除非您告诉编译器它来自哪里,否则它在实例化模板之前根本无法检查模板的有效性。这就是为什么你必须告诉编译器它来自哪里。