C++11
问题
虽然 C++03 中关于何时需要 typename 和 template 的规则在很大程度上是合理的,但它的表述有一个恼人的缺点
template<typename T>
struct A {
typedef int result_type;
void f() {
// error, "this" is dependent, "template" keyword needed
this->g<float>();
// OK
g<float>();
// error, "A<T>" is dependent, "typename" keyword needed
A<T>::result_type n1;
// OK
result_type n2;
}
template<typename U>
void g();
};
可以看出,我们需要消歧关键字,即使编译器可以自己完美地判断出A::result_type只能是int(因此是一种类型),而this->g只能是成员模板g稍后声明(即使 A 在某处明确特化,也不会影响该模板中的代码,因此其含义不会受到 A 的后续特化的影响!)。
当前实例化
为了改善这种情况,在 C++11 中,当类型引用封闭模板时,语言会进行跟踪。要知道,这个类型一定是用某种形式的名字形成的,也就是它自己的名字(上面的A、A<T>、::A<T>)。已知由这样的名称引用的类型是当前实例化.如果形成名称的类型是成员/嵌套类(那么,A::NestedClass 和A 都是当前实例化),则可能有多个类型都是当前实例化。
基于这个概念,语言表示CurrentInstantiation::Foo、Foo和CurrentInstantiationTyped->Foo(例如A *a = this; a->Foo)都是当前实例化的成员 如果发现它们是当前实例化的类或其非依赖基类之一的成员(通过立即进行名称查找)。
如果限定符是当前实例化的成员,则不再需要关键字 typename 和 template。这里要记住的一个关键点是A<T>是仍然类型相关的名称(毕竟T 也是类型相关的)。但是 A<T>::result_type 被认为是一种类型——编译器将“神奇地”查看这种依赖类型来解决这个问题。
struct B {
typedef int result_type;
};
template<typename T>
struct C { }; // could be specialized!
template<typename T>
struct D : B, C<T> {
void f() {
// OK, member of current instantiation!
// A::result_type is not dependent: int
D::result_type r1;
// error, not a member of the current instantiation
D::questionable_type r2;
// OK for now - relying on C<T> to provide it
// But not a member of the current instantiation
typename D::questionable_type r3;
}
};
这令人印象深刻,但我们可以做得更好吗?语言甚至走得更远需要一个实现在实例化D::f时再次查找D::result_type(即使它已经在定义时找到了它的含义)。如果现在查找结果不同或导致歧义,则程序格式错误,必须给出诊断。想象一下如果我们这样定义C会发生什么
template<>
struct C<int> {
typedef bool result_type;
typedef int questionable_type;
};
在实例化D<int>::f 时,编译器需要捕获错误。所以你得到了两个世界中最好的:“延迟”查找保护你,如果你可能遇到依赖基类的麻烦,以及“立即”查找,让你从typename和template中解放出来。
未知专业
在D的代码中,名字typename D::questionable_type不是当前实例化的成员。相反,语言将其标记为未知专业的成员.特别是,当您执行 DependentTypeName::Foo 或 DependentTypedName->Foo 并且依赖类型是不是当前的实例化(在这种情况下,编译器可以放弃并说“我们稍后会看看 Foo 是什么)或者它是在它或其非依赖基类中找不到当前实例化和名称,并且还有依赖基类。
想象一下如果我们在上面定义的A类模板中有一个成员函数h会发生什么
void h() {
typename A<T>::questionable_type x;
}
在 C++03 中,该语言允许捕获此错误,因为永远不会有有效的方法来实例化 A<T>::h(无论您向 T 提供什么参数)。在 C++11 中,该语言现在有进一步的检查,以便为编译器提供更多理由来实现此规则。由于A没有依赖基类,并且A声明没有成员questionable_type,所以名称A<T>::questionable_type是两者都不当前实例化的成员也不未知专业的成员。在那种情况下,该代码不可能在实例化时有效编译,因此语言禁止限定符是当前实例化的名称既不是未知特化的成员也不是当前实例化的成员(但是, 这种违规仍然不需要诊断)。
例子和琐事
您可以在 this answer 上尝试了解这些知识,并在真实世界的示例中查看上述定义对您是否有意义(在该答案中重复的细节略有减少)。
C++11 规则使以下有效的 C++03 代码格式错误(这不是 C++ 委员会的意图,但可能不会修复)
struct B { void f(); };
struct A : virtual B { void f(); };
template<typename T>
struct C : virtual B, T {
void g() { this->f(); }
};
int main() {
C<A> c; c.g();
}
这个有效的 C++03 代码会在实例化时将 this->f 绑定到 A::f,一切都很好。然而,C++11 立即将其绑定到 B::f 并在实例化时需要仔细检查,检查查找是否仍然匹配。但是,当实例化 C<A>::g 时,将应用 Dominance Rule,查找将找到 A::f。