Effective C++ (E3 42)笔记之了解typename的双重意义、嵌套类型的使用

时间:2022-04-19 15:44:03
template<typename T> class Widget;
template<class T>class Widget;

对于这两个声明式,在声明模板参数时,关键字class和typename意义完全相同

然后有时,却只能使用typename。如有以下模板:

template<typename C>
void print2nd(const C& container)
{
if(container.size() >= 2) {
typename C::const_iterator iter(container.begin()); //nested dependent name
++iter;
int val = *iter; //non-independent
cout<<val<<endl;

//C::const_iterator* x; //error, need typename, default isnt a typename regarded by compiler
}
}

“C::const_iterator iter”和“C::const_iterator* x” 被称为嵌套从属类型名称。就是说,如果模板内的变量iter/x的类型是C::const_iterator, 实际是什么类型必须取决于模板参数C,称为从属名称。此外从属名称又在class内呈嵌套状,称为嵌套从属名称(此名称指涉某类型)。而“int val“的int不依赖任何模板参数,称为 非从属名称

对于嵌套从属名称,编译器不知道“C::const_iterator“到底是个变量还是类型,默认不是类型。如果C恰好有个static变量名字就叫C::const_iterator,同时有个全局变量x,于是"C::const_iterator* x"变成了乘法操作。因此,用户必须明确在嵌套从属名称前加typename,告诉编译器它是个类型。并且typename只能被验明嵌套从属名称

template<typename C>
void func0(const C& container, typename C::const_iterator iter) //1st para cannot add typename, 2st para must add typename
{
if(container.size() >= 2){
iter = container.begin();
++iter;
int val = *iter;
cout<<val<<endl;
}
}

上例中,第一个参数不能加typename而第二个参数必须加typename。

typename加在嵌套从属名称前的例外是,typename不可以出现在基类列表内以及成员初值列表内

template<typename T>
class Base
{
public:
class Nested{
public:
Nested(int var=0)
:x(var){}
void printNes(){
cout<<"Base<"<<typeid(T).name()<<"> Nested x: "<<x<<endl;
}
int x;
};
void printBase(){
cout<<"printBase"<<endl;
}

};

template<typename T>
class Derived:public Base<T>::Nested{ //base class list, forbid typename
public:
typedef typename Base<T>::Nested BTN;
explicit Derived(int x)
//:Base<T>::Nested(x) //member init list, forbid typename
:BTN(x)
{
//typename Base<T>::Nested temp;
BTN temp(77);
temp.printNes();
}
};

上例,声明Derived的基类列表和声明Derived构造函数的成员初值列表中,Base<T>::Nested就是一个嵌套从属名称。但这两种场合加typename将编译错误。

这里还对typename使用了typedef:

typedef typename Base<T>::Nested BTN;
以缩短定义这种冗长的嵌套从属名称,方便定义其对象。


那么该如何使用嵌套在Base<T>中的Nested类型呢?其实很简单,Nested和Base<T>其实没有任何关系,只是语法上使用类作用域符限定,应该将其看作两个独立的类型

可以定义Derived对象(Derived其实继承自Nested,而不是Base):

	Derived<list<char>> der0(10);
//der0.printBase(); //err,class Derived isnt inheritted from Base, but from Nested
der0.printNes(); //so it can call bases Nested func

也可以定义Nested对象:

	Base<int>::Nested nes0(20);
nes0.printNes();
Base<std::string>::Nested nes1(30);
nes1.printNes();

结果:

Effective C++ (E3 42)笔记之了解typename的双重意义、嵌套类型的使用

可见Derived对象和Nested对象都无法调用Base<T>的成员函数,确实和Base类没关系,Base类压根没有实例化。


tips: typeid(T).name()用来获取一个类型/表达式的名称