提问人:cheshirekow 提问时间:8/7/2009 最后编辑:Sorangwala Abbasalicheshirekow 更新时间:8/29/2016 访问量:17977
为什么派生模板类无权访问基模板类的标识符?
Why doesn't a derived template class have access to a base template class' identifiers?
问:
考虑:
template <typename T>
class Base
{
public:
static const bool ZEROFILL = true;
static const bool NO_ZEROFILL = false;
}
template <typename T>
class Derived : public Base<T>
{
public:
Derived( bool initZero = NO_ZEROFILL ); // NO_ZEROFILL is not visible
~Derived();
}
我无法使用 GCC g++ 3.4.4 (cygwin) 编译它。
在将它们转换为类模板之前,它们是非泛型的,派生类能够看到基类的静态成员。这是 C++ 规范要求中的可见性损失,还是我需要采用语法更改?
我知道每个实例化都会有自己的静态成员“”和“”,这是不同的变量,但我并不在乎;常量用于代码的可读性。我想使用静态常量,因为它在名称冲突方面比宏或全局更安全。Base<T>
ZEROFILL
NO_ZEROFILL
Base<float>::ZEROFILL
Base<double>::ZEROFILL
答:
似乎在 vs 2008 中编译正常。你试过吗:
public:
Derived( bool initZero = Base<T>::NO_ZEROFILL );
评论
typename
这对您来说是两阶段查找。
Base<T>::NO_ZEROFILL
(所有大写标识符都是 boo,除了宏,顺便说一句)是一个依赖于 的标识符。
由于当编译器首次解析模板时,还没有实际的类型替换,因此编译器“不知道”是什么。因此,它无法知道您假定在其中定义的任何标识符(编译器稍后才看到某些标识符可能存在特殊化),并且您不能从基类中定义的标识符中省略基类限定。T
T
Base<T>
T
这就是为什么你必须写(或)。这告诉编译器是基类中的内容,它依赖于 ,并且它只能在以后实例化模板时对其进行验证。因此,它将接受它,而无需尝试验证代码。
只有在以后通过提供 的实际参数来实例化模板时,才能验证该代码。Base<T>::NO_ZEROFILL
this->NO_ZEROFILL
NO_ZEROFILL
T
T
评论
:^>
::
::
typename
您遇到的问题是由于依赖基类的名称查找规则造成的。14.6/8 具有:
在查找模板定义中使用的名称声明时,通常的查找规则(3.4.1、 3.4.2) 用于非依赖性名称。依赖于模板参数的名称查找是 推迟到知道实际的模板参数 (14.6.2)。
(这并不是真正的“两阶段查找” - 有关解释,请参见下文。
关于 14.6/8 的要点是,就编译器而言,在您的示例中是一个标识符,不依赖于模板参数。因此,根据 3.4.1 和 3.4.2 中的正常规则查找它。NO_ZEROFILL
这种正常的查找不会在内部搜索,因此NO_ZEROFILL只是一个未声明的标识符。14.6.2/3 具有:Base<T>
在类模板或类模板的成员的定义中,如果类模板的基类 依赖于 template-parameter,则在非限定名称查找期间不会检查基类范围 在定义类模板或成员时,或在类模板的实例化过程中 或成员。
当您实质上限定 时,您正在将其从非依赖名称更改为依赖名称,当您这样做时,您会延迟其查找,直到模板实例化。NO_ZEROFILL
Base<T>::
旁注:什么是两阶段查找:
void bar (int);
template <typename T>
void foo (T const & t) {
bar (t);
}
namespace NS
{
struct A {};
void bar (A const &);
}
int main ()
{
NS::A a;
foo (a);
}
上面的例子编译如下。编译器解析 的函数体,并查看是否存在具有依赖参数的调用(即依赖于模板参数的调用)。此时,编译器根据 3.4.1 查找 up,这是“阶段 1 查找”。查找将找到该函数,该函数将与依赖调用一起存储,直到以后。foo
bar
void bar (int)
当模板随后实例化时(作为 调用的结果),编译器随后在参数范围内执行额外的查找,这就是“阶段 2 查找”。这种情况导致发现 .main
void NS::bar(A const &)
编译器有两个重载,它会在它们之间进行选择,在上面的例子中调用 .bar
void NS::bar(A const &)
评论
bar
foo
bar
foo
void bar (int);
bar
foo(T const &)
template <typename T> void bar (T);
namespace NS
template <typename T> void bar (T);
bar
NS::bar
bar(T)
bar(T)
foo
bar(int)
试试这个程序
#include<iostream>
using namespace std;
template <class T> class base{
public:
T x;
base(T a){x=a;}
virtual T get(void){return x;}
};
template <class T>
class derived:public base<T>{
public:
derived(T a):base<T>(a){}
T get(void){return this->x+2;}
};
int main(void){
base<int> ob1(10);
cout<<ob1.get()<<endl;
derived<float> ob(10);
cout<<ob.get();
return 0;
}
在行中,还可以使用范围分辨率 (::) 运算符。例如,尝试将该行替换为T get(void){return this->x+2;}
T get(void){return base<T>::x+2;}
评论
上一个:特定成员的模板专业化?
评论
Base<T>::NO_ZEROFILL
this->NO_ZEROFILL
using Base<T>::NO_ZEROFILL