typename关键字

《Effective C++》条款42:了解typename的双重意义 中,对此进行了介绍,首先明确 typename 关键字只在 c++ 的模板和泛型编程中才会出现。


在学习STL的过程当中,经常会遇到一些让人难以理解的C++代码,比如:

typedef typename std::vector<T>::size_type size_type;

看起来它应该是定义一个类型别名,但是typedef使用方式应该是typedef + 原类型名 + 新类型名(typedef具体使用方法可移步 typedef和#define的用法与区别 ),为何此处多了个typename?typename又是什么东西?

typedef char* PCHAR;

只记得泛型编程(GP)当中,若是对模板类/函数进行定义时,可以使用template<class T>或者template<typename T>,且这两者是等价的。
那么如上语句中的typename关键字是起了什么作用呢?


1. std::vector<T>::size_type 是什么?

查看侯捷的《STL源码剖析》一书,其为:

template <class T,class Alloc=alloc>
class vector{
public:
    //...
    typedef size_t size_type;
    //...
};

原来vector::size_typevector中的嵌套类型,其实际等价于STL中大面积使用的可跨平台的size_t类型。


2. 使用 typename 关键字的意义

将语句中的typename关键字抛开不看,则语句为:

typedef std::vector<T>::size_type size_type;

可以感觉到是对std::vector<T>::size_type这个类型进行重命名。那么为什么要加上typename这个关键字呢?

原来,其中T是模板中的类型参数,它只有等到模板实例化时才会知道是哪种类型,更不用说T内部的size_type。所以对于限定依赖名(解释见补充)std::vector<T>::size_type而言,无法判断其是静态成员变量/函数还是嵌套类型,这样会造成语句的二义性,这是不能够容忍的。

若是在std::vector<T>::size_type这个类型前加上typename这一关键字,指明这是一个嵌套类型,而不是T的静态成员或静态成员函数,消除了二义性


3. 总结

根据以上的分析,可以知道语句的含义和作用是:
typedef创建了存在类型的别名,而typename告诉编译器std::vector<T>::size_type是一个类型而不是一个成员。


拓展:为何会有typename关键字

若是不加 typename 时,在如下模板定义会造成语句的二义性:

template <class T>
void foo() {
    T::iterator * iter;
    // ...

}

struct ContainsAnotherType {
    static int iterator;
    // ...

};

然后如此实例化foo的类型参数:

foo<ContainsAnotherType>();

那么,T::iterator * iter;被编译器实例化为ContainsAnotherType::iterator * iter;。在这里,前面是一静态成员变量而不是类型,那么这便成了一个乘法表达式,不过iter在这里没有定义,编译器会报错:

error C2065: ‘iter’ : undeclared identifier

但如果iter是一个全局变量,那么这行代码将完全正确,它是表示计算两数相乘的表达式,返回值被抛弃。

如上造成了同一行代码能以两种完全不同的方式解释,而且在模板实例化之前,完全没有办法来区分它们。

为解决该问题,C++标准委员会引入了typename关键字,使得模板类/函数在实例化前就区分其内部的依赖名(例如:vector<T>::iterator viter;, 也称dependent names)为静态成员变量/函数还是嵌套类型。


补充:类外部访问类内成员或名称、(非)限定名、(非)依赖名

一、 类外部访问类内成员或名称

在类作用域概念中,在类外部访问类中的名称时,可以使用类作用域操作符::,形如MyClass::name的调用通常存在三种:①静态数据成员②静态成员函数③嵌套类型

struct MyClass {
    static int A;
    static int B();
    typedef int C;
}

MyClass::A, MyClass::B, MyClass::C分别对应着上面三种。


二、限定名和非限定名

限定名(qualified name),就是限定了命名空间的名称。看下面这段代码,coutendl就是限定名:

#include <iostream>
int main()  {
    std::cout << "Hello world!" << std::endl;
}

coutendl前面都有std::,它限定了std这个命名空间,因此称其为限定名。

如果在上面这段代码中,前面用using std::cout;或者using namespace std;,然后使用时只用coutendl,它们的前面不再有空间限定std::,所以此时的coutendl就叫做非限定名(unqualified name)


三、依赖名和非依赖名

依赖名(dependent name) 是指依赖于模板参数的名称,而 非依赖名(non-dependent name) 则相反,指不依赖于模板参数的名称。看下面这段代码:

template <class T>
class MyClass {
    int i;
    vector<int> vi;
    vector<int>::iterator vitr;

    T t;
    vector<T> vt;
    vector<T>::iterator viter;
};

因为是内置类型,所以类中前三个定义的类型在声明这个模板类时就已知。然而对于接下来的三行定义,只有在模板实例化时才能知道它们的类型,因为它们都依赖于模板参数T。因此,T, vector<T>vector<T>::iterator称为依赖名。前三个定义叫做非依赖名。

更为复杂一点,如果用了typedef T U; U u;,虽然T没再出现,但是U仍然是依赖名。由此可见,不管是直接还是间接,只要依赖于模板参数,该名称就是依赖名。


参考资料

  1. 知无涯之C++ typename的起源与用法 (推荐)

  2. 《STL源码剖析》

  3. 《C++ Primer(第6版)》

  4. Where and why do I have to put the “template” and “typename” keywords? 中的 answer #1225

  5. C++ typedef typename 作用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值