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既不是当前实例化的成员,也不是未知特化的成员。在这种情况下,代码不可能在实例化时有效编译,因此语言禁止限定符为当前实例化的名称既不是未知专门化的成员,也不是当前实例化的成员(然而,这种违反仍然不需要诊断)。
例子和琐事
您可以在这个答案中尝试这些知识,并在现实世界的示例中查看上述定义是否对您有意义(在该答案中,它们重复了一些不太详细的内容)。
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代码将在实例化时将->f绑定到A::f,一切正常。然而,c++ 11立即将其绑定到B::f,并要求在实例化时进行双重检查,检查查找是否仍然匹配。然而,当实例化C<A>::g时,优势规则应用,查找将找到A::f。