C++关键字typename的深入理解

问题:在下面的 template declarations(模板声明)中 class 和 typename 有什么不同?


代码如下:

template<class T> class Widget; // uses "class"
template<typename T> class Widget; // uses "typename"

答案:没什么不同。在声明一个 template type parameter(模板类型参数)的时候,class 和 typename 意味着完全相同的东西。一些程序员更喜欢在所有的时间都用 class,因为它更容易输入。其他人(包括我本人)更喜欢 typename,因为它暗示着这个参数不必要是一个 class type(类类型)。少数开发者在任何类型都被允许的时候使用 typename,而把 class 保留给仅接受 user-defined types(用户定义类型)的场合。但是从 C++ 的观点看,class 和 typename 在声明一个 template parameter(模板参数)时意味着完全相同的东西。
然而,C++ 并不总是把 class 和 typename 视为等同的东西。有时你必须使用 typename。为了理解这一点,我们不得不讨论你会在一个 template(模板)中涉及到的两种名字。
假设我们有一个函数的模板,它能取得一个 STL-compatible container(STL 兼容容器)中持有的能赋值给 ints 的对象。进一步假设这个函数只是简单地打印它的第二个元素的值。它是一个用糊涂的方法实现的糊涂的函数,而且就像我下面写的,它甚至不能编译,但是请将这 些事先放在一边——有一种方法能发现我的愚蠢:


代码如下:

template<typename C> // print 2nd element in
void print2nd(const C& container) // container;
{
// this is not valid C++!
if (container.size() >= 2) {
C::const_iterator iter(container.begin()); // get iterator to 1st element
++iter; // move iter to 2nd element
int value = *iter; // copy that element to an int
std::cout << value; // print the int
}
}

我突出了这个函数中的两个 local variables(局部变量),iter 和 value。iter 的类型是 C::const_iterator,一个依赖于 template parameter(模板参数)C 的类型。一个 template(模板)中的依赖于一个 template parameter(模板参数)的名字被称为 dependent names(依赖名字)。当一个 dependent names(依赖名字)嵌套在一个 class(类)的内部时,我称它为 nested dependent name(嵌套依赖名字)。C::const_iterator 是一个 nested dependent name(嵌套依赖名字)。实际上,它是一个 nested dependent type name(嵌套依赖类型名),也就是说,一个涉及到一个 type(类型)的 nested dependent name(嵌套依赖名字)。
print2nd 中的另一个 local variable(局部变量)value 具有 int 类型。int 是一个不依赖于任何 template parameter(模板参数)的名字。这样的名字以 non-dependent names(非依赖名字)闻名。(我想不通为什么他们不称它为 independent names(无依赖名字)。如果,像我一样,你发现术语 "non-dependent" 是一个令人厌恶的东西,你就和我产生了共鸣,但是 "non-dependent" 就是这类名字的术语,所以,像我一样,转转眼睛放弃你的自我主张。)
nested dependent name(嵌套依赖名字)会导致解析困难。例如,假设我们更加愚蠢地以这种方法开始 print2nd:


代码如下:

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

这看上去好像是我们将 x 声明为一个指向 C::const_iterator 的 local variable(局部变量)。但是它看上去如此仅仅是因为我们知道 C::const_iterator 是一个 type(类型)。但是如果 C::const_iterator 不是一个 type(类型)呢?如果 C 有一个 static data member(静态数据成员)碰巧就叫做 const_iterator 呢?再如果 x 碰巧是一个 global variable(全局变量)的名字呢?在这种情况下,上面的代码就不是声明一个 local variable(局部变量),而是成为 C::const_iterator 乘以 x!当然,这听起来有些愚蠢,但它是可能的,而编写 C++ 解析器的人必须考虑所有可能的输入,甚至是愚蠢的。
直到 C 成为已知之前,没有任何办法知道 C::const_iterator 到底是不是一个 type(类型),而当 template(模板)print2nd 被解析的时候,C 还不是已知的。C++ 有一条规则解决这个歧义:如果解析器在一个 template(模板)中遇到一个 nested dependent name(嵌套依赖名字),它假定那个名字不是一个 type(类型),除非你用其它方式告诉它。缺省情况下,nested dependent name(嵌套依赖名字)不是 types(类型)。(对于这条规则有一个例外,我待会儿告诉你。)
记住这个,再看看 print2nd 的开头:


代码如下:

template<typename C>
void print2nd(const C& container)
{
if (container.size() >= 2) {
C::const_iterator iter(container.begin()); // this name is assumed to
... // not be a type

这为什么不是合法的 C++ 现在应该很清楚了。iter 的 declaration(声明)仅仅在 C::const_iterator 是一个 type(类型)时才有意义,但是我们没有告诉 C++ 它是,而 C++ 就假定它不是。要想转变这个形势,我们必须告诉 C++ C::const_iterator 是一个 type(类型)。我们将 typename 放在紧挨着它的前面来做到这一点:


代码如下:

template<typename C> // this is valid C++
void print2nd(const C& container)
{
if (container.size() >= 2) {
typename C::const_iterator iter(container.begin());
...
}
}

通用的规则很简单:在你涉及到一个在 template(模板)中的 nested dependent type name(嵌套依赖类型名)的任何时候,你必须把单词 typename 放在紧挨着它的前面。(重申一下,我待会儿要描述一个例外。)
typename 应该仅仅被用于标识 nested dependent type name(嵌套依赖类型名);其它名字不应该用它。例如,这是一个取得一个 container(容器)和这个 container(容器)中的一个 iterator(迭代器)的 function template(函数模板):


代码如下:

template<typename C> // typename allowed (as is "class")
void f(const C& container, // typename not allowed
typename C::iterator iter); // typename required

C 不是一个 nested dependent type name(嵌套依赖类型名)(它不是嵌套在依赖于一个 template parameter(模板参数)的什么东西内部的),所以在声明 container 时它不必被 typename 前置,但是 C::iterator 是一个 nested dependent type name(嵌套依赖类型名),所以它必需被 typename 前置。
"typename must precede nested dependent type names"(“typename 必须前置于嵌套依赖类型名”)规则的例外是 typename 不必前置于在一个 list of base classes(基类列表)中的或者在一个 member initialization list(成员初始化列表)中作为一个 base classes identifier(基类标识符)的 nested dependent type name(嵌套依赖类型名)。例如:


代码如下:

template<typename T>
class Derived: public Base<T>::Nested {
// base class list: typename not
public: // allowed
explicit Derived(int x)
: Base<T>::Nested(x) // base class identifier in mem
{
// init. list: typename not allowed
typename Base<T>::Nested temp; // use of nested dependent type
... // name not in a base class list or
} // as a base class identifier in a
... // mem. init. list: typename required
};

这样的矛盾很令人讨厌,但是一旦你在经历中获得一点经验,你几乎不会在意它。
让我们来看最后一个 typename 的例子,因为它在你看到的真实代码中具有代表性。假设我们在写一个取得一个 iterator(迭代器)的 function template(函数模板),而且我们要做一个 iterator(迭代器)指向的 object(对象)的局部拷贝 temp,我们可以这样做:


代码如下:

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

不要让 std::iterator_traits<IterT>::value_type 吓倒你。那仅仅是一个 standard traits class(标准特性类)的使用,用 C++ 的说法就是 "the type of thing pointed to by objects of type IterT"(“被类型为 IterT 的对象所指向的东西的类型”)。这个语句声明了一个与 IterT objects 所指向的东西类型相同的 local variable(局部变量)(temp),而且用 iter 所指向的 object(对象)对 temp 进行了初始化。如果 IterT 是 vector<int>::iterator,temp 就是 int 类型。如果 IterT 是 list<string>::iterator,temp 就是 string 类型。因为 std::iterator_traits<IterT>::value_type 是一个 nested dependent type name(嵌套依赖类型名)(value_type 嵌套在 iterator_traits<IterT> 内部,而且 IterT 是一个 template parameter(模板参数)),我们必须让它被 typename 前置。
如果你觉得读 std::iterator_traits<IterT>::value_type 令人讨厌,就想象那个与它相同的东西来代表它。如果你像大多数程序员,对多次输入它感到恐惧,那么你就需要创建一个 typedef。对于像 value_type 这样的 traits member names(特性成员名),一个通用的惯例是 typedef name 与 traits member name 相同,所以这样的一个 local typedef 通常定义成这样:


代码如下:

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

很多程序员最初发现 "typedef typename" 并列不太和谐,但它是涉及 nested dependent type names(嵌套依赖类型名)规则的一个合理的附带结果。你会相当快地习惯它。你毕竟有着强大的动机。你输入 typename std::iterator_traits<IterT>::value_type 需要多少时间?
作为结束语,我应该 提及编译器与编译器之间对围绕 typename 的规则的执行情况的不同。一些编译器接受必需 typename 时它却缺失的代码;一些编译器接受不许 typename 时它却存在的代码;还有少数的(通常是老旧的)会拒绝 typename 出现在它必需出现的地方。这就意味着 typename 和 nested dependent type names(嵌套依赖类型名)的交互作用会导致一些轻微的可移植性问题。
Things to Remember
·在声明 template parameters(模板参数)时,class 和 typename 是可互换的。
·用 typename 去标识 nested dependent type names(嵌套依赖类型名),在 base class lists(基类列表)中或在一个 member initialization list(成员初始化列表)中作为一个 base class identifier(基类标识符)时除外。

(0)

相关推荐

  • 详解C++的模板中typename关键字的用法

    typename的使用场合 用处1, 用在模板定义里, 标明其后的模板参数是类型参数. 例如 template<typename T, typename Y> T foo(const T& t, const Y& y){//....}; templace<typename T> class CTest { private: T t; public: //... } 其实,这里最常用的是使用关键字class,而且二者功能完全相同,这里的class和定义类时的class

  • C++的template模板中class与typename关键字的区别分析

    在C++模板中,可以使用class或者typename来声明模板参数,那么这两个关键字有什么区别呢? 模板参数声明 对于模板参数声明,这两个参数没有区别,含义是一样的. template class Simple; template class Simple; 上面两行都是声明一个模板类Simple. 表明类型 假如我们有这样一段代码: template void add(const T &acontainer, T &sum) { T::const_iterator iter = con

  • C++函数模板与类模板实例解析

    本文针对C++函数模板与类模板进行了较为详尽的实例解析,有助于帮助读者加深对C++函数模板与类模板的理解.具体内容如下: 泛型编程(Generic Programming)是一种编程范式,通过将类型参数化来实现在同一份代码上操作多种数据类型,泛型是一般化并可重复使用的意思.泛型编程最初诞生于C++中,目的是为了实现C++的STL(标准模板库). 模板(template)是泛型编程的基础,一个模板就是一个创建类或函数的蓝图或公式.例如,当使用一个vector这样的泛型类型或者find这样的泛型函数

  • C++可变参数的函数与模板实例分析

    本文实例展示了C++可变参数的函数与模板的实现方法,有助于大家更好的理解可变参数的函数与模板的应用,具体内容如下: 首先,所谓可变参数指的是函数的参数个数可变,参数类型不定的函数.为了编写能处理不同数量实参的函数,C++提供了两种主要的方法:如果所有的实参类型相同,可以传递一个名为initializer_list的标准库类型:如果实参的类型不同,我们可以编写可变参数模板.另外,C++还有一种特殊的省略符形参,可以用它传递可变数量的实参,不过这种一般只用于与C函数交互的接口程序. 一.可变参数函数

  • C++标准模板库函数sort的那些事儿

    STL里面有个sort函数,可以直接对数组排序,复杂度为n*log2(n).sort()定义在在头文件<algorithm>中.sort函数是标准模板库的函数,已知开始和结束的地址即可进行排序,可以用于比较任何容器(必须满足随机迭代器),任何元素,任何条件,执行速度一般比qsort要快.另外,sort()是类属函数,可以用于比较任何容器,任何元素,任何条件. 具体事例如下:char ch[20]="sdasdacsdasdas";cout<<ch<<

  • 浅析C++中模板的那点事

    1.什么是模板 假设现在我们完成这样的函数,给定两个数x和y求式子x^2 + y^2 + x * y的值 .考虑到x和y可能是 int , float 或者double类型,那么我们就要完成三个函数: int fun(int x,int y);float fun(float x,float y);double fun(double x,double y); 并且每个fun函数内部所要完成的操作也是极其的相似.如下: 复制代码 代码如下: int fun(int x,int y){    int

  • C++中的类模板详解及示例

    C++中的函数模板 对于类的声明来说,也有同样的问题.有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,如下面语句声明了一个类: 复制代码 代码如下: class Compare_int{ public:  Compare(int a,int b)  {   x=a;   y=b;  }   int max()  {   return (x>y)?x:y;  }  int min()  {   return (x<y)?x:y;  } private:  int x,y;}; 其作用是

  • C++模板之特化与偏特化详解

    前言 说到C++模板,这个已经不是什么新东西了,自己在实际开发中也用过:对于C++模板特化和偏特化,对于别人来说,已经不是什么新东西了,但是对于我来说,的确是我的盲区,那天在群里讨论这个问题,自己对于这部分确实没有掌握,又联想到在<STL源码剖析>一书中,对于此也是有着介绍.所以,今天就对此进行详细的总结,以备后忘. C++模板 说到C++模板特化与偏特化,就不得不简要的先说说C++中的模板.我们都知道,强类型的程序设计迫使我们为逻辑结构相同而具体数据类型不同的对象编写模式一致的代码,而无法抽

  • C++类模板与模板类深入详解

    1.在c++的Template中很多地方都用到了typename与class这两个关键字,而且有时候二者可以替换,那么是不是这两个关键字完全一样呢? 事实上class用于定义类,在模板引入c++后,最初定义模板的方法为:template<class T>,这里class关键字表明T是一个类型,后来为了避免class在这两个地方的使用可能给人带来混淆,所以引入了typename这个关键字,它的作用同class一样表明后面的符号为一个类型,这样在定义模板的时候就可以使用下面的方式了:      t

  • C++大数模板(推荐)

    分别使用C++中的运算符重载的方法来实现大数之间的数学运算,包括加法.减法.乘法.除法.n次方.取模.大小比较.赋值以及输入流.输出流的重载..并且使用这个大数模板,顺利AC了HDOJ上的1134这个题目的Catalan数计数问题..http://acm.hdu.edu.cn/showproblem.php?pid=1134大数模板的代码如下: 复制代码 代码如下: #include<iostream> #include<string> #include<iomanip>

  • C++中函数模板的用法详细解析

    定义 我们知道函数的重载可以实现一个函数名多用,将功能相同或者类似函数用同一个名来定义.这样可以简化函数的调用形式,但是程序中,仍然需要分别定义每一个函数. C++提供的函数模板可以更加简化这个过程. 所谓函数模板实际上是建立一个通用函数,其涵涵素类型额形参类型不具体指定,用一个虚拟的类型来代表,这个通用函数就称为函数模板. 凡是函数体相同的函数都可以用这个模板来代替,不必定义多个函数,只需要在模板中定义一次即可.在调用函数时,系统会根据实参的类型来取代模板中的虚拟类型,从而实现了不同函数的功能

  • 深入分析:C++模板究竟会使代码膨胀吗

    今天和同事说到C++模板会使代码膨胀, 可同事觉得不会.同事的依据是: 如果模板会使代码膨胀, 那么ATL和WTL里为什么还要大量使用模板? 同样功能 ,ATL和WTL编译出的可执行文件可比MFC编译的要小的多.我当时一愣 ,事实确实如同事所说,难道模板会使代码膨胀的观点是错误的吗? MFC因为本身代码量和复杂性在那里, 所以它生成比较大的exe无可厚非.我们这里重点关注为什么ATL/WTL使用模板,但是却不会使生成的exe变大. 我们知道使用模板时, 同一模板生成不同的模板实类后会是多份代码

  • C++模板特例化应用实例

    模板特例化是C++程序设计中一个非常重要的应用,本文就以实例形式对其进行分析,相信对大家进一步理解C++程序设计能够带来一定的帮助.具体内容如下: 首先,模板是C++中一个很重要的特性,写一份代码能用于多种数据类型(包括用户自定义类型).例如,STL的sort()函数可以用于多种数据类型的排序,类stack可以用作多种数据类型的栈.但是,如果我们想对特定的数据类型执行不同的代码(而不是通用模板)呢?这种情况下就可以使用模板特例化(template specialization). 一.函数模板特

  • c++中typename和class的区别介绍

    相信学习C++的人对class这个关键字都非常明白,class用于定义类.在模板引入c++后,最初定义模板的方法为: template<class T>...... 在这里class关键字表明T是一个类型,后来为了避免class在这两个地方的使用可能给人带来混淆,所以引入了typename这个关键字.它的作用同class一样表明后面的符号为一个类型,这样在定义模板的时候就可以使用下面的方式了: template<typename T>...... 在模板定义语法中关键字class与

随机推荐