《Effective C++》学习笔记(条款42:了解 typename 的双重定义)

最近开始看《Effective C++》,为了方便以后回顾,特意做了笔记。若本人对书中的知识点理解有误的话,望请指正!!!

声明 template 类型参数,class 和 typename 的意义完全相同,如:

template<class T> class Widget;		//使用 class
template<typename T> class Widget;	//使用typename

然而,C++并不总是把 class 和 typename 视为等价。有时候你一定得使用 typename 。为了了解使用的时机,我们来说一下在 template 内指涉的两种名称。

假设,我们有个模板函数,接受一个 STL 兼容容器为参数,容器内持有的对象可被赋值为 int,它的功能是打印第二元素值,它通过不了编译(原因后面会讲):

template<typename C>
void print2nd(const C& container)		//注意:它不能通过编译
{
    if(container.size() >= 2)  {
        C::const_iterator iter(container.begin());
        ++iter;
        int value = *iter;
        std::cout<<value;
    }
}

我们来谈一下上述函数中的两个局部变量 iter 和 value:

  • iter 的类型是 C::const_iterator,实际的类型取决于 template参数 C。template 内出现的名称如果相依于某个 template参数,称之为从属名称 (dependent name)(如 template参数 C),如果从属名称在 class 内呈嵌套状,我们称它为嵌套从属名称(nested dependent name),而 C::const_iterator 就是这样的一个名称。

  • value 是int类型,并不依赖任何 template参数,这叫非从属名称(non-dependent name)。

嵌套从属名称 可能会导致 解析困难,举个例子:

template<typename C>
void print2nd(const C& container)
{
    C::const_iterator* x;
    ...
}

看起来好像我们声明 x 为一个局部变量,它是个指针,指向一个 C::const_iterator ,编译器会这样认为吗?

如果 C::const_iterator 不是个类型,且有个 static 成员变量恰巧被命名为 const_iterator,或如果 x 恰巧是个 全局变量呢?

那样的话,上述代码不再是声明一个 局部变量,而是一个相乘动作:C::const_iterator 乘以 x。

在我们知道 C 是什么之前,没有任何办法可以知道 C::const_iterator 是否为一个类型。而当编译器开始解析 template print2nd 时,尚未知道 C 是什么东西。当然,C++有规则可以解析这个歧义状态:如果解析器在 template 中遭遇一个嵌套从属名称,它便假设这名称不是个 类型,除非你告诉它是。所以,默认情况下嵌套从属名称不是类型。

template<typename C>
void print2nd(const C& container)	//不能通过编译
{
    if(container.size() >= 2) {
    	C::const_iterator iter(container.begin());	//这个名称被假设为 非类型
		...
    }
}

这样你就清楚第一个例子为什么不能通过编译了吧。iter 声明式只有在 C::const_iterator 是个类型时才合理。

如果我们想告诉编译器 C::const_iterator 是个类型,就必须在紧邻它的前一个位置加上关键字 typename

template<typename C>
void print2nd(const C& container)	//合法代码,能够通过编译
{
    if(container.size() >= 2) {
    	typename C::const_iterator iter(container.begin());	//这个名称被假设为 非类型
		...
    }
}

所以,你想在 template 中指涉一个嵌套从属类型名称,就必须在紧邻它的前一个位置加上关键字 typename。

typename 只被用来验明嵌套从属类型名称,其他名称不该有它存在。例如下面的函数模板,接受一个容器和一个”指向该容器“的迭代器:

template<typename C>				//允许使用 typename 或 class
void f(const C& container,			//不允许使用 typename
      typename C::const_iterator)	//C::const_iterator 时嵌套从属名称,必须以 typename 为前导

typename 必须作为嵌套从属类型名称的前缀词 ,这一规则 有一个例外,typename 不可以出现在 base_class list 内的嵌套从属名称类型名称之前,也不可在 member initialization list(成员初始化列表)中作为 base class 修饰符,如:

template<typename T>
class Derived: public Base<T>::Neted  {  		// base class list中 不允许 typename
public:
    explicit Derived(int x): Base<T>::Nested(x) // 成员初始化列表中 不允许 typename
    {	// 嵌套从属名称既不在 base class list,也不在成员初始化列表中,作为一个基类修饰符需加上 typename
        typename Base<T>::Nested temp;  
        ...
    }
    ...
};

接下来再看一个典型例子。假设写一个 function template,让它接受一个迭代器,我们将为这个迭代器所指涉的对象做一份 局部l副本 temp:

template<typename IterT>
void workWithIterator(IterT iter)
{
    typename std::iterator_trains<IterT>::value_type temp(*iter);
    //std::iterator_trains<IterT>::value_type 是个标准的 traits class(见条款47)的一种运用
    //相当于说"类型为 IterT 的对象所指之物的类型"
    //这个语句意思是声明一个局部变量 temp,temp 的类型是 IterT 对象所指物的相同类型,并将 temp 初始化为 iter 所指物
    //如果 IterT 是 vector<int>::iterator,temp 的类型就是 int
    //如果 IterT 是 list<string>::iterator,temp 的类型就是 string
    ...
}

由于 std::iterator_trains<IterT>::value_type 是个嵌套从属类型名称(value_type 被嵌套于 iterator_trains<IterT> 之内,而 IterT 是个 template参数 ),所以我们必须在它前面加上 typename。

我们可以用 typedef 来为 std::iterator_trains<IterT>::value_type 取个别名,如:

template<typename IterT>
void workWithIterator(IterT iter)
{
    typedef typename std::iterator_trains<IterT>::value_type value_type;
    value_type temp(*iter);
    ...
}

Note:

  • 声明 template参数 时,前缀关键字 class 和 typename 可互换
  • 请使用关键字 typename 标识嵌套从属类型名称;但不得在 base class list 或 member initialization list 内以它作为base class 修饰符

条款43:学习处理模板化基类内的名称

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值