为什么我必须通过 this 指针访问模板基类成员? [英] Why do I have to access template base class members through the this pointer?

查看:28
本文介绍了为什么我必须通过 this 指针访问模板基类成员?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如果下面的类不是模板,我可以在 derived 类中简单地使用 x.但是,对于下面的代码,我必须使用this->x.为什么?

If the classes below were not templates I could simply have x in the derived class. However, with the code below, I have to use this->x. Why?

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

推荐答案

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

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

>

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

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.

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

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.

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

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:

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

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

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 int then the above code is ill-formed and must be diagnosed, just as it would be if foo wasn't a template at all, but a plain function.

现在,标准规定依赖于模板参数的名称必须在第一阶段可解析.A这里不是依赖名称,它指的是不管 T 类型如何,都是一样的.所以需要在定义模板之前定义,以便在第一阶段找到和检查.

Now, the standard says that names which aren't dependent on template parameters must be resolvable in phase 1. A here 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.

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

T::A would 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 T in 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 typename to specify that they are types, or used in certain unambiguous contexts. For example in template <typename T> struct Foo : T::A {};, T::A is used as a base class and hence is unambiguously a type. If Foo is instantiated with some type that has a data member A instead 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).

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

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

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

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

Is A a dependent name or not? With base classes, any name 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 name in Foo is dependent, and hence every type used in Foo (except built-in types) has to be qualified. Inside of Foo, you'd have to write:

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

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

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

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

A second problem with allowing your preferred code (return x;) is that even if Bar is defined before Foo, and x isn't a member in that definition, someone could later define a specialization of Bar for 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 Bar had x, they could define a specialization without it, and your template would look for a global x to return in Foo<Baz>. I think this was judged to be just as surprising and distressing as the problem you have, but it's silently surprising, as opposed to throwing a surprising error.

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

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 A dependent:

  • 在类中使用 Bar::A; - A 现在指的是 Bar 中的某些内容,因此依赖.
  • Bar<T>::A *x = 0; 在使用点 - 同样,A 肯定在 Bar 中>.这是乘法,因为没有使用 typename,所以可能是一个不好的例子,但我们必须等到实例化才能找出 operator*(Bar<T>::A,x) 返回一个右值.谁知道呢,也许确实如此……
  • this->A; 在使用点 - A 是一个成员,所以如果它不在 Foo 中,它必须是在基类中,标准再次说明这使其具有依赖性.
  • using Bar<T>::A; in the class - A now refers to something in Bar<T>, hence dependent.
  • Bar<T>::A *x = 0; at point of use - Again, A is definitely in Bar<T>. This is multiplication since typename wasn'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 - A is a member, so if it's not in Foo, it must be in the base class, again the standard says this makes it dependent.

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

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.

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

You could reasonably argue that in your example, return x; doesn't make sense if x is 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.

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

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::string could 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.

这篇关于为什么我必须通过 this 指针访问模板基类成员?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆