前言
这篇文章旨在成为litb的文章的一个易于阅读的替代品。
基本目的是一样的;必须应用typename和template来解释“什么时候?”和“为什么?”
typename和template的目的是什么?
Typename和template在声明模板以外的情况下可用。
c++中有一些特定的上下文,必须显式地告诉编译器如何处理一个名称,所有这些上下文都有一个共同点;它们依赖于至少一个模板参数。
在解释上可能有歧义的地方,我们把这样的名称称为;“依赖的名字”。
这篇文章将解释依赖名称和这两个关键字之间的关系。
一个片段超过1000个单词
试着向你自己、朋友或你的猫解释下面的函数模板中发生了什么;在标记为(A)的语句中发生了什么?
template<class T> void f_tmpl () { T::foo * x; /* <-- (A) */ }
这可能不像人们想象的那么简单,更具体地说,求值(A)的结果很大程度上取决于作为模板参数T传递的类型的定义。
不同的t可以极大地改变所涉及的语义。
struct X { typedef int foo; }; /* (C) --> */ f_tmpl<X> ();
struct Y { static int const foo = 123; }; /* (D) --> */ f_tmpl<Y> ();
这是两个不同的场景:
如果我们用X类型实例化函数模板,就像在(C)中一样,我们将声明一个指向int的指针X,但是;
如果我们用类型Y实例化模板,如(D), (A)将由一个表达式组成,该表达式计算123乘以某个已经声明的变量x的乘积。
基本原理
c++标准关心我们的安全和福祉,至少在这种情况下是这样。
为了防止实现可能遭受令人讨厌的意外,标准要求我们在任何希望将名称作为类型名称或模板id处理的地方显式声明意图,从而对依赖名称的模糊性进行排序。
如果没有声明,依赖名称将被认为是变量或函数。
如何处理依赖名称?
如果这是一部好莱坞电影,依赖名字将是一种通过身体接触传播的疾病,立即影响宿主,使其困惑。混乱可能,可能,导致一个畸形的人-,嗯…程序。
依赖名称是直接或间接依赖于模板形参的任何名称。
template<class T> void g_tmpl () {
SomeTrait<T>::type foo; // (E), ill-formed
SomeTrait<T>::NestedTrait<int>::type bar; // (F), ill-formed
foo.data<int> (); // (G), ill-formed
}
在上面的代码片段中,我们有四个相互依赖的名字:
E)
"type"依赖于SomeTrait<T>的实例化,其中包括T,和;
F)
"NestedTrait",这是一个模板id,依赖于SomeTrait<T>,和;
(F)末尾的“type”依赖于NestedTrait,而NestedTrait依赖于SomeTrait<T>,并且;
G)
"data",看起来像一个成员函数模板,间接是一个依赖名称,因为foo的类型依赖于SomeTrait<T>的实例化。
如果编译器将依赖名称解释为变量/函数,那么语句(E), (F)或(G)都是无效的(如前所述,如果我们没有显式地表示相反,则会发生这种情况)。
解决方案
要使g_tmpl具有有效的定义,必须显式地告诉编译器,我们期望在(E)中有一个类型,在(F)中有一个模板id和类型,在(G)中有一个模板id。
template<class T> void g_tmpl () {
typename SomeTrait<T>::type foo; // (G), legal
typename SomeTrait<T>::template NestedTrait<int>::type bar; // (H), legal
foo.template data<int> (); // (I), legal
}
每当一个名称表示一个类型时,所有涉及的名称必须是类型名称或名称空间,记住这一点,很容易看出我们在完全限定名称的开头应用了typename。
然而,模板在这方面是不同的,因为没有办法得出这样的结论;"哦,这是一个模板,那么这个东西也一定是模板"这意味着我们直接在任何名称前应用template,我们想这样对待。
我能把关键字放在任何名字前面吗?
“我能把typename和template放在任何名字前面吗?”我不想担心它们出现的背景……”-一些c++开发人员
标准中的规则规定,只要您处理的是限定名称(K),就可以应用关键字,但如果名称不是限定的,则应用程序是病态形式(L)。
namespace N {
template<class T>
struct X { };
}
N:: X<int> a; // ... legal
typename N::template X<int> b; // (K), legal
typename template X<int> c; // (L), ill-formed
注意:在不需要的上下文中应用typename或template并不被认为是好的实践;仅仅因为你能做某件事,并不意味着你就应该去做。
此外,还有一些上下文显式禁止typename和template:
当指定类继承的基类时
在派生类的base-specifier-list中写入的每个名称都已被视为类型-name,显式指定typename既格式错误,又冗余。
// .------- base-specifier-list
模板<class T> // v
struct派生:typename SomeTrait<T>::type /* <-格式错误*/ {
...
};
当template-id是派生类的using-directive中引用的模板id时
结构基{
模板类T > <
结构类型{};
};
派生结构:基{
使用Base::模板类型;/ /不规范的
使用基础::类型;/ /法律
};