typename与typedef typename详解

Wesley13
• 阅读 538

"**typename**"是一个C++程序设计语言中的关键字。当用于泛型编程时是另一术语"class"的同义词[1]这个关键字用于指出模板声明(或定义)中的非独立名称(dependent names)是类型名,而非变量名。以下是对于泛型编程typename两种迥然不同的用法的解释。

class关键字的同义词

这是一项C++编程语言的泛型编程(或曰“模板编程”)的功能,typename关键字用于引入一个模板参数,例如:

[cpp] view plain copy

  1. // 定义一个返回参数中较大者的通用函数
  2. template   
  3. const T& max(const T& x, const T& y)  
  4. {  
  5. if (y < x) {  
  6. return x;  
  7.   }  
  8. return y;  
  9. }

 

这种情况下,typename可用另一个等效的关键字class代替,如下代码片段所示:

[cpp] view plain copy

  1. // 定义一个返回参数中较大者的通用函数
  2. template   
  3. const T& max(const T& x, const T& y)  
  4. {  
  5. if (y < x) {  
  6. return x;  
  7.   }  
  8. return y;  
  9. }

 

以上两段代码没有功能上的区别。

类型名指示符

考虑下面的错误代码:

[cpp] view plain copy

  1. template   

  2. void foo(const T& t)  

  3. {  

  4. // 声明一个指向某个类型为T::bar的对象的指针

  5.   T::bar * p;  

  6. }  

  7. struct StructWithBarAsType {  

  8. typedef int bar;  

  9. };  

  10. int main() {  

  11.    StructWithBarAsType x;  

  12.    foo(x);  

  13. }

 

这段代码看起来能通过编译,但是事实上这段代码并不正确。因为编译器并不知道T::bar究竟是一个类型的名字还是一个某个变量的名字。究其根本,造成这种歧义的原因在于,编译器不明白T::bar到底是不是“模板参数的非独立名字”,简称“非独立名字”。[2]注意,任何含有名为“bar”的项的类T,都可以被当作模板参数传入foo()函数,包括typedef类型、枚举类型或者变量等。

为了消除歧义,C++语言标准规定:

A name used in a template declaration or definition and that is dependent on a template-parameter is assumed not to name a type unless the applicable name lookup finds a type name or the name is qualified by the keyword typename.

意即出现上述歧义时,编译器将自动默认bar为一个变量名,而不是类型名。所以上面例子中的代码 T::bar * p 会被解释为乘法,而不是声明p为指向T::bar类型的对象的指针

如果还有另一个名为StructWithBarAsValue类型,如下:

[cpp] view plain copy

  1. struct StructWithBarAsValue {  
  2. int bar;  
  3. };

 

那么,编译器将以完全不同的方式来解释

[cpp] view plain copy 

  
  
 
   
   T::bar * p  

  
  

的含义。

解决问题的最终办法,就是显式地告诉编译器,T::bar是一个类型名。这就必须用typename关键字,例如:

[cpp] view plain copy 

  
  
 
   
   template <typename T>  
 
   
   void foo(const T& t)  
 
   
   {  
 
   
      // 声明一个指向某个类型为T::bar的对象的指针  
 
   
      typename T::bar * p;  
 
   
   }  

  
  

这样,编译器就确定了T::bar是一个类型名,p也就自然地被解释为指向T::bar类型的对象的指针了。

参考文献

  1. ^ C++ Primer 4th Edition, 模板和泛型编程,模板定义
  2. ^ http://pages.cs.wisc.edu/~driscoll/typename.html
  1. 转自:http://zh.wikipedia.org/zh-cn/Typename

  1. C++箴言:理解typename的两个含义

    (zz) C++箴言:理解typename的两个含义

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

    [cpp] view plain copy

    1. template class Widget; // uses "class"
    2. template 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 的对象。进一步假设这个函数只是简单地打印它的第二个元素的值。它是一个用糊涂的方法实现的糊涂的函数,而且就像我下面写的,它甚至不能编译,但是请将这些事先放在一边——有一种方法能发现我的愚蠢:

    [cpp] view plain copy

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

     

      我突出了这个函数中的两个 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:

    [cpp] view plain copy

    1. template  
    2. void print2nd(const C& container)  
    3. {  
    4.  C::const_iterator * x;  
    5. //
    6. }

     

      这看上去好像是我们将 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 的开头:

    [cpp] view plain copy

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

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

    [cpp] view plain copy

    1. template // this is valid C++

    2. void print2nd(const C& container)  

    3. {  

    4. if (container.size() >= 2) {  

    5. typename C::const_iterator iter(container.begin());  

    6. }  

    7. }

     

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

    [cpp] view plain copy

    1. template // typename allowed (as is "class")
    2. void f(const C& container, // typename not allowed
    3. 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(嵌套依赖类型名)。例如:

    [cpp] view plain copy

    1. template  

    2. class Derived: public Base::Nested {   

    3. // base class list: typename not allowed

    4. public:  

    5. explicit Derived(int x)     //成员初始化列表包括对父类的初始化,对象成员的初始化,本类中的常量的初始化。

    6.   : Base::Nested(x) // base class identifier in mem init. list: typename not allowed

    7.   {   

    8.           typename Base::Nested temp; // use of nested dependent type

    9.   } 

    10. };

     

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

    [cpp] view plain copy

    1. template  

    2. void workWithIterator(IterT iter)  

    3. {  

    4. typename std::iterator_traits::value_type temp(*iter);

    5. }

     

      不要让 std::iterator_traits::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::iterator,temp 就是 int 类型。如果 IterT 是 list::iterator,temp 就是 string 类型。因为 std::iterator_traits::value_type 是一个 nested dependent type name(嵌套依赖类型名)(value_type 嵌套在 iterator_traits 内部,而且 IterT 是一个 template parameter(模板参数)),我们必须让它被 typename 前置。
      如果你觉得读 std::iterator_traits::value_type 令人讨厌,就想象那个与它相同的东西来代表它。如果你像大多数程序员,对多次输入它感到恐惧,那么你就需要创建一个 typedef。对于像 value_type 这样的 traits member names(特性成员名),一个通用的惯例是 typedef name 与 traits member name 相同,所以这样的一个 local typedef 通常定义成这样:

    [cpp] view plain copy

    1. template  

    2. void workWithIterator(IterT iter)  

    3. {  

    4. typedef typename std::iterator_traits::value_type value_type;  

    5.  value_type temp(*iter);  

    6. }

     

      很多程序员最初发现 "typedef typename" 并列不太和谐,但它是涉及 nested dependent type names(嵌套依赖类型名)规则的一个合理的附带结果。你会相当快地习惯它。你毕竟有着强大的动机。你输入 typename std::iterator_traits::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(基类标识符)时除外。

    转自:http://www.cppblog.com/abware/archive/2007/04/16/22020.html

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
Opencv中Mat矩阵相乘——点乘、dot、mul运算详解
Opencv中Mat矩阵相乘——点乘、dot、mul运算详解2016年09月02日00:00:36 \牧野(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fme.csdn.net%2Fdcrmg) 阅读数:59593
Wesley13 Wesley13
2年前
P2P技术揭秘.P2P网络技术原理与典型系统开发
Modular.Java(2009.06)\.Craig.Walls.文字版.pdf:http://www.t00y.com/file/59501950(https://www.oschina.net/action/GoToLink?urlhttp%3A%2F%2Fwww.t00y.com%2Ffile%2F59501950)\More.E
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这