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

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

问题描述

如果下面的类不是模板,我可以简单地在派生类中有 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;
}


推荐答案

make 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.

现在,标准说的是 aren' 这里不是一个依赖的名称,它指的是相同的事情,不管类型<$ c $ c> T 。因此,需要在定义模板之前定义模板,才能在阶段1中找到和检查模板。

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< typename T> 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).

但是,有一个依赖基类的类模板呢? p>

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

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

是否是依赖名称?使用基类,任何名称可能会出现在基类中。所以我们可以说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 依赖名称,因此假定为非类型,除非另有规定。 Ouch!

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

允许您的首选代码( return x; )的第二个问题是,即使 Bar Foo 之前定义, x 有人可以稍后为某些类型 Baz 定义 Bar 的专业化,以使 Bar< Baz> 有一个数据成员 x ,然后实例化 Foo< Baz> 。所以在实例化中,你的模板将返回数据成员,而不是返回全局 x 。或者相反,如果 Bar 的基本模板定义具有 x ,则它们可以定义没有它的特殊化,查找全局 x 以在 Foo 中返回。我认为这被认为是与您遇到的问题一样令人惊讶和痛苦,但它是 惊奇,而不是引发一个惊人的错误。

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 searched for names unless the names are already dependent for some other reason. 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 中使用Bar :: A; ,因此依赖。

  • Bar< T> :: A * x = 0; use - 再次, A 绝对在 Bar< T> 。这是乘法,因为 typename 没有被使用,所以可能是一个坏的例子,但是我们必须等到实例化以确定是否 operator * (Bar T :: A,x)返回右值。

  • this-> A; 在使用时 - A 是一个成员,所以如果它不在 Foo ,它必须在基类中,再次标准说这使它依赖。 / li>
  • 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 是基类中的嵌套类型,则返回x; 没有意义,因此语言应该依赖名称和(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天全站免登陆