简短的回答:为了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 是一个类型,则声明一个指针(除了隐藏全局变量之外没有任何作用)x
)。如果 A 是一个对象,那就是乘法(除非某些运算符重载它是非法的,分配给右值)。如果是错误的,必须诊断这个错误在第一阶段,它被标准定义为错误在模板中,而不是在它的某些特定实例中。即使模板从未实例化,如果 A 是int
那么上面的代码是错误的,必须进行诊断,就像如果foo
根本不是一个模板,而是一个简单的函数。
现在,标准规定了哪些名称aren't依赖于模板参数必须可以在第 1 阶段解决。A
这里不是一个从属名称,它指的是同一事物,无论类型如何T
。所以需要在定义模板之前定义它,以便在第一阶段被找到和检查。
T::A
将是一个依赖于 T 的名称。在第一阶段我们不可能知道这是否是一个类型。最终将用作的类型T
在实例化中,很可能尚未定义,即使定义了,我们也不知道哪种类型将用作模板参数。但我们必须解决语法问题,以便对格式错误的模板进行宝贵的第一阶段检查。因此,标准对依赖名称有一条规则 - 编译器必须假设它们是非类型,除非用typename
指定他们are类型,或在某些明确的上下文中使用。例如在template <typename T> struct Foo : T::A {};
, T::A
用作基类,因此明确是一种类型。如果Foo
使用具有数据成员的某种类型进行实例化A
而不是嵌套类型 A,这是执行实例化的代码中的错误(第 2 阶段),而不是模板中的错误(第 1 阶段)。
但是带有依赖基类的类模板又如何呢?
template <typename T>
struct Foo : Bar<T> {
Foo() { A *x = 0; }
};
A 是否是从属名称?有了基类,any名称可以出现在基类中。因此我们可以说 A 是一个从属名称,并将其视为非类型。这会产生不良影响每个名字Foo 中是依赖的,因此每种类型Foo 中使用的(内置类型除外)必须经过限定。在 Foo 内部,您必须编写:
typename std::string s = "hello, world";
because std::string
将是一个从属名称,因此除非另有说明,否则假定为非类型。哎哟!
允许您的首选代码的第二个问题(return x;
)是即使Bar
之前已定义Foo
, and x
不是该定义中的成员,有人可以稍后定义一个专门化Bar
对于某些类型Baz
,使得Bar<Baz>
有一个数据成员x
,然后实例化Foo<Baz>
。因此,在该实例化中,您的模板将返回数据成员,而不是返回全局成员x
。或者相反,如果基本模板定义Bar
had x
,他们可以在没有它的情况下定义专业化,并且您的模板将寻找全局x
返回Foo<Baz>
。我认为这被认为与你遇到的问题一样令人惊讶和痛苦,但它是silently令人惊讶,而不是抛出令人惊讶的错误。
为了避免这些问题,有效的标准规定,除非明确要求,否则不会考虑搜索类模板的依赖基类。这会阻止一切事物仅仅因为可以在依赖库中找到而依赖它。它还具有您所看到的不良效果 - 您必须限定基类中的内容,否则找不到它。常见的制作方法有以下三种A
依赖:
-
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
,它必须位于基类中,标准再次指出这使其具有依赖性。
两阶段编译既繁琐又困难,并且对代码中的额外冗长引入了一些令人惊讶的要求。但就像民主一样,除了所有其他方式之外,它可能是最糟糕的做事方式。
您可以合理地认为,在您的示例中,return x;
没有意义如果x
是基类中的嵌套类型,因此该语言应该 (a) 说它是一个从属名称,并且 (2) 将其视为非类型,并且您的代码无需使用this->
。在某种程度上,您是不适用于您的情况的问题的解决方案所造成的附带损害的受害者,但是您的基类仍然存在问题,可能会在您下面引入影子全局变量的名称,或者没有您认为的名称他们有,并且发现了一个全球性的东西。
您还可能认为默认值应该与依赖名称相反(假设类型除非以某种方式指定为对象),或者默认值应该对上下文更加敏感(在std::string s = "";
, std::string
可以被理解为一种类型,因为没有其他任何东西具有语法意义,即使std::string *s = 0;
是有歧义的)。再说一次,我不太清楚这些规则是如何商定的。我的猜测是,所需的文本页数会减少创建许多特定规则,以确定哪些上下文采用类型,哪些上下文采用非类型。