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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4643074/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 16:01:31  来源:igfitidea点击:

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

c++templatesinheritancec++-faq

提问by Ali

If the classes below were not templates I could simply have xin the derivedclass. However, with the code below, I have touse this->x. Why?

如果下面的类不是模板,我可以简单地xderived类中使用。但是,对于下面的代码,我必须使用this->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;
}

回答by Steve Jessop

Short answer: in order to make xa dependent name, so that lookup is deferred until the template parameter is known.

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

Long answer: when a compiler sees a template, it is supposed to perform certain checks immediately, without seeing the template parameter. Others are deferred until the parameter is known. It's called two-phase compilation, and MSVC doesn't do it but it's required by the standard and implemented by the other major compilers. If you like, the compiler must compile the template as soon as it sees it (to some kind of internal parse tree representation), and defer compiling the instantiation until later.

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

The checks that are performed on the template itself, rather than on particular instantiations of it, require that the compiler be able to resolve the grammar of the code in the template.

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

In C++ (and C), in order to resolve the grammar of code, you sometimes need to know whether something is a type or not. For example:

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

if A is a type, that declares a pointer (with no effect other than to shadow the global x). If A is an object, that's multiplication (and barring some operator overloading it's illegal, assigning to an rvalue). If it is wrong, this error must be diagnosed in phase 1, it's defined by the standard to be an error in the template, not in some particular instantiation of it. Even if the template is never instantiated, if A is an intthen the above code is ill-formed and must be diagnosed, just as it would be if foowasn't a template at all, but a plain function.

如果 A 是一种类型,则声明一个指针(除了遮蔽 global 之外没有任何效果x)。如果 A 是一个对象,那就是乘法(并且除非某些运算符重载它是非法的,否则分配给右值)。如果错误,则必须在第 1 阶段诊断此错误,标准将其定义为模板中的错误,而不是它的某个特定实例。即使模板从未被实例化,如果 A 是 anint那么上面的代码是格式错误的,必须被诊断出来,就像它根本foo不是模板一样,而是一个普通的函数。

Now, the standard says that names which aren'tdependent on template parameters must be resolvable in phase 1. Ahere is not a dependent name, it refers to the same thing regardless of type T. So it needs to be defined before the template is defined in order to be found and checked in phase 1.

现在,标准说,这名依赖于模板参数必须在1阶段解析A这里是不是一个从属名称,它指的是无论哪种类型的同样的事情T。因此需要在定义模板之前进行定义,以便在阶段 1 中找到和检查。

T::Awould be a name that depends on T. We can't possibly know in phase 1 whether that's a type or not. The type which will eventually be used as Tin an instantiation quite likely isn't even defined yet, and even if it was we don't know which type(s) will be used as our template parameter. But we have to resolve the grammar in order to do our precious phase 1 checks for ill-formed templates. So the standard has a rule for dependent names - the compiler must assume that they're non-types, unless qualified with typenameto specify that they aretypes, or used in certain unambiguous contexts. For example in template <typename T> struct Foo : T::A {};, T::Ais used as a base class and hence is unambiguously a type. If Foois instantiated with some type that has a data member Ainstead of a nested type A, that's an error in the code doing the instantiation (phase 2), not an error in the template (phase 1).

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

But what about a class template with a dependent base class?

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

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

Is A a dependent name or not? With base classes, anyname could appear in the base class. So we could say that A is a dependent name, and treat it as a non-type. This would have the undesirable effect that every namein Foo is dependent, and hence every typeused in Foo (except built-in types) has to be qualified. Inside of Foo, you'd have to write:

A 是否是从属名称?对于基类,任何名称都可以出现在基类中。所以我们可以说 A 是一个依赖名称,并将其视为非类型。这会产生不良影响,即Foo中的每个名称都是相关的,因此Foo 中使用的每个类型(内置类型除外)都必须进行限定。在 Foo 里面,你必须写:

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

because std::stringwould be a dependent name, and hence assumed to be a non-type unless specified otherwise. Ouch!

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

A second problem with allowing your preferred code (return x;) is that even if Baris defined before Foo, and xisn't a member in that definition, someone could later define a specialization of Barfor some type Baz, such that Bar<Baz>does have a data member x, and then instantiate Foo<Baz>. So in that instantiation, your template would return the data member instead of returning the global x. Or conversely if the base template definition of Barhad x, they could define a specialization without it, and your template would look for a global xto return in Foo<Baz>. I think this was judged to be just as surprising and distressing as the problem you have, but it's silentlysurprising, as opposed to throwing a surprising error.

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

To avoid these problems, the standard in effect says that dependent base classes of class templates just aren't considered for search unless explicitly requested. This stops everything from being dependent just because it could be found in a dependent base. It also has the undesirable effect that you're seeing - you have to qualify stuff from the base class or it's not found. There are three common ways to make Adependent:

为了避免这些问题,该标准实际上表示,除非明确要求,否则不会考虑搜索类模板的依赖基类。这阻止了一切都依赖,仅仅因为它可以在依赖库中找到。它还具有您所看到的不良影响 - 您必须从基类中限定内容,否则找不到。有三种常见的方式来建立A依赖:

  • using Bar<T>::A;in the class - Anow refers to something in Bar<T>, hence dependent.
  • Bar<T>::A *x = 0;at point of use - Again, Ais definitely in Bar<T>. This is multiplication since typenamewasn't used, so possibly a bad example, but we'll have to wait until instantiation to find out whether operator*(Bar<T>::A, x)returns an rvalue. Who knows, maybe it does...
  • this->A;at point of use - Ais a member, so if it's not in Foo, it must be in the base class, again the standard says this makes it dependent.
  • using Bar<T>::A;在类中 -A现在指的是 中的东西Bar<T>,因此依赖。
  • Bar<T>::A *x = 0;在使用点 - 同样,A绝对在Bar<T>. 这是乘法,因为typename没有使用,所以可能是一个不好的例子,但我们必须等到实例化才能确定是否operator*(Bar<T>::A, x)返回右值。谁知道呢,也许真的……
  • this->A;在使用点 -A是一个成员,所以如果它不在 中Foo,它必须在基类中,再次标准说这使它依赖。

Two-phase compilation is fiddly and difficult, and introduces some surprising requirements for extra verbiage in your code. But rather like democracy it's probably the worst possible way of doing things, apart from all the others.

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

You could reasonably argue that in your example, return x;doesn't make sense if xis a nested type in the base class, so the language should (a) say that it's a dependent name and (2) treat it as a non-type, and your code would work without this->. To an extent you're the victim of collateral damage from the solution to a problem that doesn't apply in your case, but there's still the issue of your base class potentially introducing names under you that shadow globals, or not having names you thought they had, and a global being found instead.

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

You could also possibly argue that the default should be the opposite for dependent names (assume type unless somehow specified to be an object), or that the default should be more context sensitive (in std::string s = "";, std::stringcould be read as a type since nothing else makes grammatical sense, even though std::string *s = 0;is ambiguous). Again, I don't know quite how the rules were agreed. My guess is that the number of pages of text that would be required, mitigated against creating a lot of specific rules for which contexts take a type and which a non-type.

您也可能会争辩说,依赖名称的默认值应该是相反的(假设类型,除非以某种方式指定为对象),或者默认值应该更加上下文敏感(在std::string s = "";,std::string可以被视为一种类型,因为没有其他东西使语法意义,即使std::string *s = 0;是模棱两可的)。同样,我不太清楚规则是如何达成一致的。我的猜测是,需要的文本页数可以减少为上下文采用类型和非类型创建许多特定规则。

回答by Ali

(Original answer from Jan 10, 2011)

(2011 年 1 月 10 日的原始回答)

I think I have found the answer: GCC issue: using a member of a base class that depends on a template argument. The answer is not specific to gcc.

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



Update:In response to mmichael's comment, from the draft N3337of the C++11 Standard:

更新:响应mmichael 的评论,来自C++11 标准的N3337 草案

14.6.2 Dependent names [temp.dep]
[...]
3 In the definition of a class or class template, if a base class depends on a template-parameter, the base class scope is not examined during unqualified name lookup either at the point of definition of the class template or member or during an instantiation of the class template or member.

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

Whether "because the standard says so"counts as an answer, I don't know. We can now ask why the standard mandates that but as Steve Jessop's excellent answerand others point out, the answer to this latter question is rather long and arguable. Unfortunately, when it comes to the C++ Standard, it is often nearly impossible to give a short and self-contained explanation as to why the standard mandates something; this applies to the latter question as well.

无论是“因为标准是这样说的”算作一个答案,我不知道。我们现在可以问为什么标准要求如此,但正如Steve Jessop和其他人所指出的那样,后一个问题的答案相当长且值得商榷。不幸的是,当谈到 C++ 标准时,通常几乎不可能给出一个简短而独立的解释,说明为什么标准要求某些东西。这也适用于后一个问题。

回答by chrisaycock

The xis hidden during the inheritance. You can unhide via:

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

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

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