【C++】模板中template、typename的非常见用法

模板是指:C++程序设计中采用类型作为参数的程序设计,用于支持通用程序设计。C++的标准库提供许多有用的函数大多结合了模板的观念,如STL、IO Stream等。在模型的声明中,最最最常见的用法就是:

template <typename T>

值得注意的是:模板在编译的时候并不知道这个T是什么类型,只有在实例化的时候才能够知晓

因此,这会导致在模板较复杂的时候,可能会产生很多意想不到的理解上的歧义。为了规避这种歧义性,typename和template在某些特定的情况下,需要点明。这就是它们的非常见用法。


typename

先来看第一个歧义性的用法:

template <typename T>
class MyClass {
  T::SubType *ptr;
};

这里使用了类作用域操作符::,形如MyClass::name的用法通常存在三种:静态数据成员、静态成员函数和嵌套类型。例如:

class MyClass {
  static int A;       // 静态数据成员
  static int B();     // 静态成员函数
  typedef int C;      // 嵌套类型
}

那么请问,上面的例子中T::SubType指的哪一种?

  • 嵌套类型:定义了T类型下的SubType嵌套类型的指针ptr
  • 静态数据成员:T类型下的静态数据成员SubType,和ptr做乘法操作

如果是第一种含义,没有问题。如果是第二种含义,那么就出问题了:ptr未定义啊,此时编译器就会报错。当然,如果ptr是一个全局变量,那么就完全没有问题。它表示的是计算两数相乘的表达式,返回值被抛弃。

同一行代码能以两种完全不同的方式进行解释,而且在模板实例化之前,完全没有办法来区分它们,这种歧义性绝对是滋生各种bug的温床。

因此,在一些编译环境下,会直接报错:

main.cpp:3:3: error: missing 'typename' prior to dependent type name 'T::SubType'
  T::SubType *ptr;
  ^~~~~~~~~~
  typename 
1 error generated.

在C++标准中规定:对于用于模板定义的依赖于模板参数的名称,只有在实例化的参数中存在这个类型名,或者这个名称前使用了typename关键字来修饰,编译器才会将该名称当成是类型。除了以上这两种情况,绝不会被当成是类型。或者可以简单理解成:除了使用typename修饰之外,template内的任何标识符号都被视为一个值/成员而不是一个类型。可以参考文档:typename MSDN

也就是说,按照C++标准上说,T::SubType只能解释为第二种含义。如果要想指定乘第一种含义,即告诉编译器SubType是嵌套类型而不是静态数据成员,只需用typename修饰:

template <typename T>
class MyClass {
  typename T::SubType *ptr;
};

此时,关键字typename被用来作为类型之前的标识符号

需要注意的是:对于不会引起歧义的情况,仍然需要在前面加typename。例如:

template <typename T>
class MyClass {
  typename T::SubType ptr;
};

此时,如果将T::SubType理解成静态数据成员的话,那就两个数据成员,好像没有什么语法是这样的。那是不是就只能理解成嵌套类型,不需要修饰typename了呢?不行,此处仍还需typename修饰。

再加深一些印象:

template<typename T, typename T2>
void f_tmpl () { 
  typename T::foo *x;         // 这里我们需要typename来告诉编译器T::foo是一个type
  typename T::foo y;          // 这里我们需要typename来告诉编译器T::foo是一个type
  T2::foo *z;                 // 这里编译器默认T2::foo不是一个type
  T t;                        // 这里我们不需要typename,编译器也知道T是一个type
  detail::apply a;            /* 这里也不需要typename,因为没有template argument
                                 一切信息已知,apply不属于dependent type
                                 所以编译器也知道这里的apply是一个type */
}

最后强调一点:typename可以由模板声明或定义中任何位置的任何类型使用。但基类列表中不允许使用它,除非作为模板基类的模板参数

例如:

template <class T>
class C1 : typename T::InnerType        // Error - typename not allowed
{};

template <class T>
class C2 : A<typename T::InnerType>     // typename OK
{};

template

再来看第二个歧义性的用法:

template <typename T>
void func(T f) {
  T::cast<int>();
}

那么请问,上面的例子中T::cast<int>()可以怎么理解?

  • 静态成员函数:T类型下的静态成员函数cast(为模板函数),实参是void
  • 静态数据成员:T类型下的静态数据成员cast,判断是否小于int

如果是第一种含义,没有问题。如果是第二种含义,那么就出问题了:一个cast的数据成员和int怎么做小于的判断啊。

同一行代码能以两种完全不同的方式进行解释,而且在模板实例化之前,完全没有办法来区分它们,这种歧义性绝对是滋生各种bug的温床。

因此,在一些编译环境下,会直接报错:

main.cpp:3:6: error: use 'template' keyword to treat 'cast' as a dependent template name
  T::cast<int>(f);
     ^
     template 
1 error generated.

在C++标准中规定:template内的任何标识符号如果没有被关键字特意修饰,都优先被视为一个值/成员(typename也是,如果没有被typename修饰,就不会被认为是类型)。

也就是说,应该告诉编译器cast是模板静态成员函数,后面的<>表示模板,只需用template修饰:

template <typename T>
void func(T f) {
  T::template cast<int>();
}

此时,关键字template被用来作为模板的标识符号

需要注意的是:template前面不一定是::,还可以是.或者->。例如:

template <typename T>
void func(T f) {
  f.template cast<int>();
  f->template cast<int>();
}

此时,cast函数就不是模板静态成员函数,而是模板普通成员函数了。当然,如果不是模板函数,而是模板类,也是可以的。


一些例子

简单总结一下:关键字typename用于点明类型,关键字template用于点明模板

STL库中iterator_traits(迭代器萃取器)判断是否为POD类型:

typedef typename __type_traits<T1>::is_POD_type is_POD;

Sophus库中求SE3类的Jacobian时的代码片段:

Vector3<Scalar> const upsilon = upsilon_omega.template head<3>();
Vector3<Scalar> const omega = upsilon_omega.template tail<3>();

ROS库代码:

typedef std::basic_string<char,
                          std::char_traits<char>,
                          typename ContainerAllocator::template rebind<char>::other
                         >
  _joint_name_type;

相关阅读

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这上传的资源包含一套我工作常用的模板库,及不需要MFC支持的excel操作接口,导出函数调用栈(dump stack)接口,可以直接用VS2008运行TestCodeLib.sln来根据unit test来了解用法。 ⑴ 需求(requirements) 重量级的BOOST非常强大,但有时候项目没有引入它,这时候我们需要自己的模板库。 BOOST is very powerful, but some projects have not include BOOST library. So we need out own template type trait library -- it is the responsibility of this lightweight library. 即使BOOST非常强大,但有些常用的功能其也没有,而经常性的代码又需要这些功能。比如把运行期数据转换为元程序需要的编译期数据。 Even if BOOST is very powerful,it can't still meet all requirements. e.g. convert runtime data into compile period data needed by metaprogramming. /*************************************************************************************************************************************/ ⑵ 益处(advantage) 此泛型库抽象了一些常用的业务需求,可以避免大量的重复工作。 它是完全泛型的并且是类型安全的(没有强制类型转换),如果使用错误将导致编译失败,从而提高了正确率(正确性由编译器保证)。 这个库的很多模板类型推导不需要C++11的支持,这是一个大的优势(VS2010才开始支持C++11)。 this general library draws out some common business and avoid unnecessary repeat work. it is completed general and type-safe(without any type cast), mistake(s) will cause compile failure, so it improves correctness. In this library , type deduce need't C++11's support, it is big advantage. (VS2010 begin to support C++11) /*************************************************************************************************************************************/ ⑶ 用法(usage) 下载这个库后,使用VS打开.\CodeLib\testcase\TestCodeLib\TestCodeLib.sln,直接按F5启动,即可以看到许多单元测试的用法/测试用例的输出。 如果需要使用某功能,可以参考其对应的测试代码的用法。(每个功能文件.\CodeLib\include\MiniMPL\xxx.hpp,都对应一个测试文件.\CodeLib\testcase\MiniMPL\test_xxx.hpp) (这个库的使用及修改是完全自由的,只需要保留文件头的注释即可) usage: download this library, open .\CodeLib\testcase\TestCodeLib\TestCodeLib.sln with VS,you can see many usage/test output of unit test. every feature has according unit test file, it shows its usage. e.g. .\CodeLib\include\MiniMPL\xxx.hpp has according ".\CodeLib\testcase\MiniMPL\test_xxx.hpp" this library is all free, the only requirement is that you need to keep the comments in header file. /*********************************************************************************************************************************************************/ ⑷ 本库提供的主要功能介绍: major feature in this lib: ◆ [typeTraits.hpp] ★ 测试类型的基本属性,比如IsConst/IsVoliate/IsRef/isAtomType/isBuildInType/isEnumType/IsIterator/IsPointer/isString/isInnerFloat/isArray/IsBaseDerive/.... ★ 转换类型的基本属性,比如AddConst/AddVoliate/AddRef/AddPointer,..,RemoveConst/RemoveVoliate/RemoveRef/RemovePointer,... 这类功能是元程序库的基本支持组件,其它库(比如boost)也提供了,但本库时提供的检测属性更多。 ☆ detect type property. e.g.IsConst/IsVoliate/IsRef/isAtomType/isBuildInType/isEnumType/IsIterator/IsPointer/isString/isInnerFloat/isArray/IsBaseDerive/.... ☆ convert type basic qualifier,e.g. AddConst/AddVoliate/AddRef/AddPointer,..,RemoveConst/RemoveVoliate/RemoveRef/RemovePointer,... get type traits.e.g.const/voliate/ref/isAtomType/isBuildInType/isEnumType/isString/isInnerFloat/isArray/IsBaseDerive/.... It is base support component of metaprogramming system,it is similiar with BOOST , but this lib provide more. ◆ [typeConvert.hpp] ★ 实现类型的修饰符转换。比如让输出参数类型的修饰符(const/voliate/ref/*)和输入参数类型的修饰符一样。 SameConst/SameVoliate/SameRef/SamePointer/SameAllQualifier/RefAdapter 应用场景:存取结构体的某类成员,当输入参数有某种const/voliate/ref修饰符时,通常要求返回值也有类似的修饰符。 ★ 当把"智能指针/stl迭代器/C指针/前三者嵌套"都视为指针时,其内的最终值(非指针值)是一致的,在模板函数,某些场景需要取得其最终的非指针值。 应用场景:转发模板函数,如 template<typename T> void transmit(T p) { receive(p); } //void receive(int&); 如果transmit的传入实参p为指针类型(比如smartpointer<vector<int*>::iterator>*或者vector<int*>::iterator), 但是转发的接收函数receive的形参为非指针类型(比如int&),理论上是可以实现转换的。 Get::finalValue接口提供了这种自动的转: template<typename T> void transmit(T p) { receive(Get::finalValue(p)); } ☆ Convert type qualifiers,e.g. addConst/removeConst.. , keep same output qualifier (const/voliate/ref/*) with input type. apply scenario: get member of one structure object. ☆ Think "stlSmartptr<T>/StlContainer<T>::iterator/T*" as pointer, their inner non-pointer value is same. in some scenario, the final non-pointer value is needed. e.g. template<typename T> void transmit(T p) { receive(p); } //void receive(int&); if real paremeter "p" is smartpointer<vector<int*>::iterator>* or vector<int*>::iterator , but needed parameter by "receive" is int&, in theory it is OK. Get::finalValue provide this conversion: template<typename T> void transmit(T p) { receive(Get::finalValue(p)); } ◆ [traverseTypeSet.hpp] ★ C++语法不支持模板函数/模板成员函数作为回调函数。本库采用了封装,可以支持模板函数的回调,并且支持最多7个可变参数(可以简易扩充参数个数)。 可以遍历一个TypeList或者枚举值范围CEnumRange,然后以满足条件的类型回调用户的模板函数。 其广泛的应用场景即是把运行期数据以一种非hard-code的方式转化为编译期数据,从而满足元程序对编译期数据的需求。 ☆ C++ doesn't support template-based callback function. this lib package support template-based callback function(MAX 7 various parameters,easy to expand). It can traverse one TypeList or enum value , then call user's template function by suitable type/enum value. This feature converts runtime data into compile data to meet metaprogramming requirement without hard-code way, it is one big advantage. ◆ [functionTraits.hpp] ★ 获取任意类型函数的各种特征,比如函数的所有参数Params_T,返回值类型Return_T,对象类型Object_T(如果是成员函数),第N个参数的类型GetFunctionParam<F,N>, 这些类型都是包含修饰符(const/voliate/ref)的完整类型。 这些组件对于操作函数非常重要。 ☆ get some traits of any function, include all parameter type "Params_T",return type "Return_T", host type "Object_T"(if member-function) , No.x parameter type "GetFunctionParam<F,x>". this type include all signature qualifiers. This component is very important for metaprogramming based on function. ◆ 有时候STL的算法并不好用,经常是为了第三个参数需要自己写一个专用的琐碎的小函数。 虽然可以用std的bind或者boost的lambda,但是对于某些嵌套情况,用起来非常麻烦,这个库提供了下面的一些解决方式: sometimes STL algorithm is not good and it needs one traival function object(third parameter) , althrough std::bind/boost::lambda is available, but for some nest case, it is very hard to be used.this library provide below features: [function.hpp] ★ 把既有的多元函数转换为一元函数对象UnaryFunction。它通常应用于泛型(比较/排序/遍历)算法的第三个参数。 ☆ convert existing multi-parameters into unary function, it is general used as 3rd parameter in general algorithm. e.g. stl::for_each [functionobject.hpp] ★ 把一些常用目的的函数封装成函数对象,比如"比较器/测试器" ☆ function object with special abstract targart. e.g. "comparer/Tester" ◆ [functionCreater.hpp] ★ 把多元函数封装为一元函数的帮助函数。(一元函数对象的类型通常不易于书写) ☆ helper function to pack multi-parameters into unary function.(it is hard to write unary function object type) ◆ [paramPackage.hpp] ★ 实现了把任意多个(最多7个,可简易扩充),任意类型的参数封装成一个参数以利于数据传递。 ☆ pack any number parameter (max 7,easy expand) into one parameter . it is easy to transfer. ◆ [classregister.hpp] ★ MFC的动态创建不是泛型的,创造出来的对象必须是CObject的派生类,而且支持的创造方式单一,不够灵活有时候甚至不能满足需求。 本库里提供了一个泛型的动态创建方式,可以以多种灵活的方式甚至用户自定义的方式来匿名动态创建对象,创建的对象基类可以由用户指定(必须存在派生关系)。 ☆ like MFC's DYNAMIC_CREATE, but the one of MFC is not general,the instance MUST be drived from class CObject, MFC dynamic creation has only one create way,sometimes it is not enough。 this library provides general dynamic create way, can create object by multiple ways , even customized way.and base class can be specified by user. ◆ [callbackWorker.hpp] ★ 最易于使用的回调函数是无参数的回调函数。 此功能可以把任意多个参数的多元(成员/非成员)函数封装成一个无参数函数,作为简单的回调函数。 ☆ best callback function is non-parameter function. This feature packs multiple-parameter function into one no-parameter function, it is easy to be used as callback function. ◆ [memberPtr.hpp] ★ 以统一的方式实现了任意级数的结构体成员的存和取,比如多级结构体嵌套。例子:a.m_b.m_c.m_d.....m_x,非常易于在模板设计使用。 ☆ access any level member of structure object by one unified way. e.g:a.m_b.m_c.m_d.....m_x,it is easy to be used in template componment. ◆ [anyObject.hpp] ★ 任意对象类(CAnyObject)。提供模板化的指针操作符,如果不支持用户指定指针类型,则转换结果为NULL,从而保证正确性。 ☆ package any object(CAnyObject), it operator function is template-based. if it doesn't support conversion, it return NULL. ◆ [dataset.hpp] ★ 把STL容器和经典数组封装成统一的形式,在使用上不再区别对待。对于C数组,将会自动检测越界情况。 ★ 可以使用初始化列表对数组,STL容器进行(反复)初始化。例如:vector<int> a={1,2,3,45,2}; ☆ pack STL container and class array into unified object with several same interfaces. ☆ can initialize array/stl container with initalization list repeated. e.g. vector<int> a={1,2,3,45,2}; ◆ [macroLoop.hpp] ★ 当多条语句的差别仅仅是一个数字时,可以利用提供的循环宏简化成一条宏语句,从而简化书写。用法可参对应的单元测试例子。 ☆ if only one number is different in multiple statements, can use one macro loop to simplify them (one macro statement) usage refer to unit test. ◆ [mathOperator.hpp] ★ 泛型的数学操作符。"equal/lesser/NotBinary/NotUnary/notEqual/lesserEqual/greater/greaterEqual及交换函数swap/swapif" ☆ general math operator. "equal/lesser/NotBinary/NotUnary/notEqual/lesserEqual/greater/greaterEqual and swap/swapif" /*************************************************************************************************************************************/ ⑸ 感谢及借鉴: 本库的占位符[placeHolder.hpp]借鉴于boost库,感谢boost库的大师们的灵感。 typelist来自loki库,但是把命名空间Loki改为MiniMPL以避免频繁的命名域切入/切出,感谢Andrei Alexandrescu的精彩演绎与启发. thanks and borrow: Args [placeHolder.h] comes from BOOST::MPL. thanks for BOOST team. typelist comes from loki lib with tiny modification(rename namespace loki to MiniMPL to avoid field switch frequently).thanks for Andrei Alexandrescu
包含定义代码和测试代码,代码不复杂,有注释,池对象存取时间复杂度为常数级,多线程测试速度可达千万次/秒。 使用方法如下: 1.定义池,例如: FastPool<std::string> pool; 2.往池添加对象,例如: pool.Add("abc");//要注意这里传入的是构造对象的参数 3.取出一个对象以使用,例如: std::string* p = pool.Pop(); 这一步可以跟上一步结合起来: std::string* p = pool.AddAndPop("123");//这时候p就指向"123"这个刚加入池的std::string对象 4.存回对象以重用,例如: pool.Release(p); 5.还有的时候需要删除池的对象,例如: pool.Delete(p);//只删除一个对象 pool.DeleteNotUsed();//删除池未使用的对象 pool.DeleteAll();//删除池所有对象 上面的示例只是模板参数为std::string 时的用法。还有一些函数未列出。希望代码能给大家带来启发。 重新检查了一次,发现模板代码区分类和基本数据类型的代码用到了另外自定义的库。需要做如下修改: #include<type_traits> //先定义一个模板用以区分类和基本数据类型的行为 template<class TT, bool val>struct ChangeClass {typedef typename TT Type;}; template<class TT>struct ChangeClass<TT,false> {typedef typename std::vector<void*> Type;}; 然后代码的 typedef typename LK::Templates::UseT<LK::Templates::IsClassOrUnion<T>::value, std::vector<void>, T>::type TP; if (LK::Templates::IsClassOrUnion<T>::value) 改为: typedef typename ChangeClass<T, std::is_class<T>::value>::Type TP;//防止非类调用析构函数导致的错误 if (std::is_class<T>::value) 最后,欢迎喜欢编程的伙伴来qq群244953928探讨O(∩_∩)O
C++实现属性 本文译自http://www.codeguru.com/cpp_mfc/Property.html的Implementing a Property in C++ 以下是译文 本文由Emad Barsoum投稿。 开发测试环境:Visual C++ 7.0, Windows XP sp1, Windows 2000 sp3 摘要 本文试着在C++不使用任何扩展技术模拟C#(或其他语言)的属性特征。大多数在C++实现属性的库和编译器使用扩展技术,如Managed C++C++ Builder,或者他们使用如通常函数的set和get方法,但那不是属性。 详述 我们首先看一下什么是属性。一个属性表现为一个字段或者成员变量,但它通过read和write方法或者get和set方法暗操作变量。 例如,若存在类A和它的属性Count,我可以写如下的代码: A foo; Cout << foo.Count; 实际上Count调用它的get函数返回当前的变量值。你可以将属性定为只读(你可以读取它但不能修改它)、只写或者可读写,这就是使用属性而不直接使用变量的的一个最大好处了。好了,让我们开始来实现它: 我们需要能做如下的事: int i = foo.Count; //--调用get函数得到值 foo.Count = i; //-- 调用set函数设定值 因此,很明显的我们需要重载 = 操作符使其能设定变量的值,同时也要重载该属性的返回值(在下面我们将会看到的)。 我们将实现一个称为property的类,它做的就像一个属性,声明如下: template class property {} 这个模板类表示的是我们的属性。Container是我们要在其包含属性的类变量,set和get方法以及属性的类的类型。ValueType是内部变量即要定义的属性的类型,nPropType定义属性的读写标志:只读、只写或可读写。 现在我们需要一个指向从包含属性的类Container到属性类property的set和get方法的指针,同时重载 = 操作符以使得属性能象变量起那样作用。现在我们来看property类的全部定义 #define READ_ONLY 1 #define WRITE_ONLY 2 #define READ_WRITE 3 template class property { public: property() { m_cObject = NULL; Set = NULL; Get = NULL; } //-- 将m_cObject指向包含属性的container类 -- void setContainer(Container* cObject) { m_cObject = cObject; } //-- 设定可改变属性值的set成员函数 -- void setter(void (Container::*pSet)(ValueType value)) { if((nPropType == WRITE_ONLY) || (nPropType == READ_WRITE)) Set = pSet; else Set = NULL; } //-- 设定可检索属性值的get成员函数 -- void getter(ValueType (Container::*pGet)()) { if((nPropType == READ_ONLY) || (nPropType == READ_WRITE)) Get = pGet; else Get = NULL; } //-- 重载 = 号操作符使其能用set成员设定属性值-- ValueType operator =(const ValueType& value) { assert(m_cObject != NULL); assert(Set != NULL); (m_cObject->*Set)(value); return value; } //-- 使属性类能转换为内部类型成为可能-- operator ValueType() { assert(m_cObject != NULL); assert(Get != NULL);

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值