为什么派生模板类无权访问基模板类的标识符?

Why doesn't a derived template class have access to a base template class' identifiers?

提问人:cheshirekow 提问时间:8/7/2009 最后编辑:Sorangwala Abbasalicheshirekow 更新时间:8/29/2016 访问量:17977

问:

考虑:

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>ZEROFILLNO_ZEROFILLBase<float>::ZEROFILLBase<double>::ZEROFILL

C 模板 派生类 C++-FAQ

评论

0赞 George Robinson 5/14/2018
由于两阶段名称查找(并非所有编译器默认使用)。此问题有 4 种解决方案:1) 使用前缀,2) 使用前缀,3) 添加语句,4) 使用启用宽松模式的全局编译器开关。 这些解决方案的优缺点在 stackoverflow.com/questions/50321788/ 中描述...Base<T>::NO_ZEROFILLthis->NO_ZEROFILLusing Base<T>::NO_ZEROFILL

答:

1赞 Alan 8/7/2009 #1

似乎在 vs 2008 中编译正常。你试过吗:

public:
    Derived( bool initZero = Base<T>::NO_ZEROFILL );

评论

5赞 sbi 8/7/2009
实际上,VC 不做两阶段查找。这就是它在那里编译的原因。这就是为什么使用 VC 创建模板库是个坏主意——当你在任何其他编译器上需要它时,你会有很多东西需要修复。
0赞 jalf 8/7/2009
不过,修复通常相当微不足道。这主要是插入大量 ' 的问题,并修复偶尔的 2 阶段查找。typename
3赞 sbi 8/8/2009
@jalf:这是真的,除非不是。除此之外,我还遇到了非常棘手的相互依赖问题,这些问题在 VC 中没有发现,因为 VC 只会在模板实例化时才真正解析模板——到那时,所有依赖实体都在范围内。在适当的两阶段查找的第一次解析中,它崩溃了,花了相当长的时间和程序员的通用疗法(间接)来解开混乱。在那之后,代码就更难理解了,如果早点知道这个问题,它可能会有不同的设计。
49赞 sbi 8/7/2009 #2

这对您来说是两阶段查找。

Base<T>::NO_ZEROFILL(所有大写标识符都是 boo,除了宏,顺便说一句)是一个依赖于 的标识符。
由于当编译器首次解析模板时,还没有实际的类型替换,因此编译器“不知道”是什么。因此,它无法知道您假定在其中定义的任何标识符(编译器稍后才看到某些标识符可能存在特殊化),并且您不能从基类中定义的标识符中省略基类限定。
TTBase<T>T

这就是为什么你必须写(或)。这告诉编译器是基类中的内容,它依赖于 ,并且它只能在以后实例化模板时对其进行验证。因此,它将接受它,而无需尝试验证代码。
只有在以后通过提供 的实际参数来实例化模板时,才能验证该代码。
Base<T>::NO_ZEROFILLthis->NO_ZEROFILLNO_ZEROFILLTT

评论

1赞 jalf 8/7/2009
哇,你把我打败了 20 秒。+1
1赞 sbi 8/7/2009
@jalf:所以这一次,我是第一个。 随意改进它。:^>
2赞 cheshirekow 8/7/2009
有趣。那么,继承的非静态成员是否需要相同的资格?即 Base<T>::memberFunction()
2赞 sbi 8/7/2009
@ceshirekow:是的,基本上 后面的所有内容,其中 之前有依赖于模板参数的东西,都是“依赖”的,这是由此产生的问题之一。(如果您想了解更多信息,请参阅有关为什么有时需要的解释。它有同样的原因,你会在任何地方找到很好的解释。::::typename
1赞 Richard Corden 8/7/2009
@sbi:您确定这称为两阶段查找吗?我一直认为该术语仅用于参数相关查找。您在标准中有这方面的参考吗?
30赞 Richard Corden 8/10/2009 #3

您遇到的问题是由于依赖基类的名称查找规则造成的。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_ZEROFILLBase<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 查找”。查找将找到该函数,该函数将与依赖调用一起存储,直到以后。foobarvoid bar (int)

当模板随后实例化时(作为 调用的结果),编译器随后在参数范围内执行额外的查找,这就是“阶段 2 查找”。这种情况导致发现 .mainvoid NS::bar(A const &)

编译器有两个重载,它会在它们之间进行选择,在上面的例子中调用 .barvoid NS::bar(A const &)

评论

4赞 Johannes Schaub - litb 12/14/2010
+1,很好的解释:)太糟糕了,您似乎已经停止在stackoverflow上回答。我喜欢你详细而深入的讨论。
0赞 Richard Corden 12/14/2010
很高兴被错过!我希望在不久的将来能重新回答问题。
0赞 Carousel 8/29/2016
@RichardCorden 我不完全理解您说明“两阶段查找”的旁注。据我了解,函数体中的函数名称是一个依赖名称,因为它的参数是类型依赖的。难道不应该将 in 的名称查找也推迟到知道实际的模板参数之前吗?实际上,clang++ 3.8.1 不会产生任何关于名称查找的错误,即使我注释掉了这一行。所以我怀疑是否真的执行了“第 1 阶段查找”。barfoobarfoovoid bar (int);bar
0赞 Richard Corden 8/30/2016
@Carousel 由于名称是相关的,因此在第 1 阶段查找期间预计不会出现错误。请尝试以下操作: 在 、 上方添加新的模板重载。更改具有相同签名中的重载:。第 1 阶段查找查找 的 2 个重载,第 2 阶段查找 。编译器无法在函数之间选择最佳匹配项。现在将全局移动到下面。代码将成功编译,因为阶段 1 仅查找 .foo(T const &)template <typename T> void bar (T);namespace NStemplate <typename T> void bar (T);barNS::barbar(T)bar(T)foobar(int)
0赞 shahnoor rahman 4/14/2015 #4

试试这个程序

#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;}

评论

1赞 stefan 4/14/2015
请正确格式化您的代码。解释代码为什么工作以及原始代码中的问题在哪里,通常也是一件好事。