为什么我必须通过 this 指针访问模板基类成员?

Why do I have to access template base class members through the this pointer?

提问人:Ali 提问时间:1/10/2011 最后编辑:DestructorAli 更新时间:10/4/2019 访问量:31702

问:

如果下面的类不是模板,我可以简单地在课堂上使用。但是,使用下面的代码,我必须使用 .为什么?xderivedthis->x

template <typename T>
class base {

protected:
    int x;
};

template <typename T>
class derived : public base<T> {

public:
    int f() { return this->x; }
};

int main() {
    derived<int> d;
    d.f();
    return 0;
}
C 模板 继承 C++-FAQ

评论

0赞 Ali 1/10/2011
@Ed Swangren:对不起,我在发布这个问题时错过了提供的答案。在此之前,我一直在寻找答案。
8赞 George Robinson 5/14/2018
发生这种情况的原因是两阶段名称查找(并非所有编译器默认使用)和依赖名称。除了在前面加上 之外,这个问题有 3 种解决方案,即:1) 使用前缀,2) 添加语句,3) 使用启用宽容模式的全局编译器开关。这些解决方案的优缺点在 stackoverflow.com/questions/50321788/ 中进行了描述xthis->base<T>::xusing base<T>::x

答:

14赞 chrisaycock 1/10/2011 #1

在继承过程中是隐藏的。您可以通过以下方式取消隐藏:x

template <typename T>
class derived : public base<T> {

public:
    using base<T>::x;             // added "using" statement
    int f() { return x; }
};

评论

31赞 jamesdlin 1/10/2011
这个答案并不能解释为什么它是隐藏的。
0赞 JDługosz 9/29/2020
我得到base<T> is not a namespace or unscoped enum
17赞 Ali 1/10/2011 #2

(原答案来自2011年1月10日)

我想我已经找到了答案:GCC 问题:使用依赖于模板参数的基类的成员。 答案并不特定于 gcc。


更新:作为对 mmichael 评论的回应,来自 C++11 标准的 N3337 草案

14.6.2 依赖名称 [temp.dep]
[...]
3 在类或类模板的定义中,如果基类依赖于 template-parameter,则在非限定名称期间不检查基类范围 在类模板的定义点查找 或成员,或在类模板或成员的实例化期间。

“因为标准这么说”算不算答案,我不知道。我们现在可以问为什么标准要求这样做,但正如史蒂夫·杰索普(Steve Jessop)和其他人的出色回答所指出的那样,后一个问题的答案相当长且值得争论。不幸的是,当涉及到 C++ 标准时,通常几乎不可能对标准为什么强制要求某些东西给出简短而独立的解释;这也适用于后一个问题。

355赞 13 revs, 2 users 99%Steve Jessop #3

简短的回答:为了创建一个依赖名称,以便将查找推迟到已知模板参数。x

长答案:当编译器看到模板时,它应该立即执行某些检查,而不会看到模板参数。其他参数将推迟到已知参数为止。它称为两阶段编译,MSVC 不这样做,但它是标准要求的,并由其他主要编译器实现。如果您愿意,编译器必须在看到模板后立即对其进行编译(某种内部解析树表示),并将编译实例化推迟到以后。

对模板本身执行的检查(而不是对模板的特定实例化执行的检查)要求编译器能够解析模板中代码的语法。

在 C++(和 C)中,为了解析代码的语法,有时需要知道某些东西是否是类型。例如:

#if WANT_POINTER
    typedef int A;
#else
    int A;
#endif
static const int x = 2;
template <typename T> void foo() { A *x = 0; }

如果 A 是一个类型,则声明一个指针(除了遮蔽全局之外没有其他作用)。如果 A 是一个对象,那就是乘法(除非某些运算符重载它是非法的,赋值给 rvalue)。如果错误,则必须在第 1 阶段诊断此错误,标准将其定义为模板中的错误,而不是模板的某些特定实例中的错误。即使模板从未实例化,如果 A 是 A,则上述代码格式不正确,必须进行诊断,就像根本不是模板,而是一个普通函数一样。xintfoo

现在,该标准规定,依赖于模板参数的名称必须在第 1 阶段可解析。 这里不是一个依赖名称,它指的是同一事物,无论类型如何。因此,需要在定义模板之前定义它,以便在第 1 阶段找到并检查。AT

T::A将是一个依赖于 T 的名称。在第一阶段,我们不可能知道这是否是一种类型。最终将在实例化中使用的类型很可能还没有定义,即使定义了,我们也不知道哪些类型将用作我们的模板参数。但是我们必须解决语法问题,以便对格式错误的模板进行宝贵的第 1 阶段检查。因此,该标准对依赖名称有一个规则 - 编译器必须假定它们是非类型,除非限定指定它们是类型,或者在某些明确的上下文中使用。例如,在 中用作基类,因此明确地是一种类型。如果使用具有数据成员而不是嵌套类型 A 的某种类型进行实例化,则这是执行实例化的代码中的错误(阶段 2),而不是模板中的错误(阶段 1)。Ttypenametemplate <typename T> struct Foo : T::A {};T::AFooA

但是,具有依赖基类的类模板呢?

template <typename T>
struct Foo : Bar<T> {
    Foo() { A *x = 0; }
};

A 是不是依赖名称?使用基类时,任何名称都可以出现在基类中。因此,我们可以说 A 是一个依赖名称,并将其视为非类型。这将产生不良效果,即 Foo 中的每个名称都是依赖的,因此 Foo 中使用的每个类型(内置类型除外)都必须经过限定。在 Foo 内部,你必须写:

typename std::string s = "hello, world";

因为 将是一个依赖名称,因此除非另有说明,否则假定为非类型。哎哟!std::string

允许首选代码 () 的第二个问题是,即使之前定义了 ,并且不是该定义中的成员,有人也可以稍后为某种类型定义 的专用化,例如确实具有数据成员,然后实例化。因此,在该实例化中,模板将返回数据成员,而不是返回全局 .或者相反,如果 had 的基本模板定义,他们可以在没有它的情况下定义专用化,并且您的模板将查找要返回的全局。我认为这被认为和你遇到的问题一样令人惊讶和痛苦,但它是默默地令人惊讶,而不是抛出一个令人惊讶的错误。return x;BarFooxBarBazBar<Baz>xFoo<Baz>xBarxxFoo<Baz>

为了避免这些问题,该标准实际上规定,除非明确请求,否则不会考虑类模板的依赖基类进行搜索。这阻止了所有事物的依赖性,因为它可以在依赖性基础中找到。它还具有您看到的不良效果 - 您必须从基类中限定内容,否则找不到它。有三种常见的方法可以使依赖:A

  • using Bar<T>::A;在类中 - 现在指的是 中的东西,因此依赖。ABar<T>
  • Bar<T>::A *x = 0;在使用点 - 同样,肯定在 .这是乘法,因为没有使用,所以可能是一个不好的例子,但我们必须等到实例化才能确定是否返回右值。谁知道呢,也许确实如此......ABar<T>typenameoperator*(Bar<T>::A, x)
  • this->A;在使用点 - 是一个成员,所以如果它不在 ,它必须在基类中,标准再次说这使得它依赖于。AFoo

两阶段编译既繁琐又困难,并且对代码中的额外措辞提出了一些令人惊讶的要求。但就像民主一样,它可能是最糟糕的做事方式,除了所有其他方式。

你可以合理地争辩说,在你的示例中,如果是基类中的嵌套类型,则没有意义,因此语言应该 (a) 说它是一个依赖名称,并且 (2) 将其视为非类型,并且您的代码可以在没有 .在某种程度上,你是因解决一个不适用于你的情况的问题而造成的附带损害的受害者,但仍然存在一个问题,即你的基类可能会在你下面引入影子全局名称,或者没有你认为它们有的名称,而是找到一个全局名称。return x;xthis->

你也可能争辩说,对于依赖名称,默认值应该相反(假设类型,除非以某种方式指定为对象),或者默认值应该更上下文相关(在 中,可以被理解为一种类型,因为没有其他任何语法意义,即使模棱两可)。同样,我不太清楚这些规则是如何达成的。我的猜测是,所需的文本页数减轻了为哪些上下文采用类型和哪些非类型创建许多特定规则的情况。std::string s = "";std::stringstd::string *s = 0;

评论

2赞 jalf 1/10/2011
哦,很好的详细答案。澄清了几件我从未费心去查找的事情。:) +1
29赞 Steve Jessop 1/10/2011
@jalf:有没有像C++QTWBFAETYNSYEWTKTAAHMITTBGOW——“除了你不确定自己甚至想知道答案并且有更重要的事情要处理之外,还会经常被问到的问题”?
7赞 Matthieu M. 1/10/2011
非凡的答案,想知道这个问题是否适合常见问题解答。
1赞 Ionoclast Brigham 7/13/2013
哇,我们能说百科全书吗?海舞不过,有一点微妙:“如果 Foo 使用具有数据成员 A 而不是嵌套类型 A 的某种类型进行实例化,则这是执行实例化的代码中的错误(第 2 阶段),而不是模板中的错误(第 1 阶段)。最好说模板没有格式错误,但这仍然可能是模板编写者不正确的假设或逻辑错误的情况。如果标记的实例化实际上是预期的用例,那么模板将是错误的。
1赞 Johannes Schaub - litb 9/15/2013
@SteveJessop基类只是不搜索非限定名称查找。换句话说,非限定名称查找,无论是否针对依赖名称,都不会查看依赖基类。