前面的博文介绍了模板的基础,深入模板特性,模板和设计的一些内容。从这篇开始,我们介绍一些高级模板设计,开发某些相对较小、并且互相独立的功能,而且对于这些简单功能而言,模板是最好的实现方法:
(1)一个用于类型区分的框架;
(2)智能指针
(3)tuple
(4)仿函数
------------------------------------------------------------------------------------------------------------

第19章 类型区分
本章主要介绍用模板实现对类型的辨识,判断其是内建类型、指针类型、class类型或者其他类型中的哪一种。
------------------------------------------------------------------------------------------------------------
19.1 辨别基本类型
缺省情况下,我们一方面假定一个类型不是一个基本类型,另一方面我们为所有的基本类型都特化给模板:

// types/type1.hpp

// 基本模板:一般情况下T不是基本类型
template <typename T>
class IsFundaT
{
public:
enum { Yes = , No = };
}; // 用于特化基本类型的宏
#define MK_FUNDA_TYPE(T) \
template<> class IsFundaT<T> { \
public: \
enum { Yes = , No = }; \
}; MK_FUNDA_TYPE(void) MK_FUNDA_TYPE(bool)
MK_FUNDA_TYPE(char)
MK_FUNDA_TYPE(signed char)
MK_FUNDA_TYPE(unsigned char)
MK_FUNDA_TYPE(wchar_t) MK_FUNDA_TYPE(signed short)
MK_FUNDA_TYPE(unsigned short)
MK_FUNDA_TYPE(signed int)
MK_FUNDA_TYPE(unsigned int)
MK_FUNDA_TYPE(signed long)
MK_FUNDA_TYPE(unsigned long) #if LONGLONG_EXISTS
MK_FUNDA_TYPE(signed long long)
MK_FUNDA_TYPE(unsigned long long)
#endif // LONGLONG_EXISTS MK_FUNDA_TYPE(float)
MK_FUNDA_TYPE(double)
MK_FUNDA_TYPE(long double) #undef MK_FUNDA_TYPE

19.2 辨别组合类型
组合类型是指一些构造自其他类型的类型。简单的组合类型包括:普通类型、指针类型、引用类型和数组类型。它们都是构造自单一的基本类型。同时,class类型和函数类型也是组合类型,但这些组合类型通常都会涉及到多种类型(例如参数或者成员的类型)。在此,我们先考虑简单的组合类型;另外,我们还将使用局部特化对简单的组合类型进行区分。接下来,我们将定义一个trait类,用于描述简单的组合类型;而class类型和枚举类型将在最后考虑。

// types/type2.hpp
template <typename T>
class CompoundT // 基本模板
{
public:
enum { IsPtrT = , IsRefT = , IsArrayT = ,
IsFuncT = , IsPtrMemT = };
typedef T BaseT;
typedef T BottomT;
typedef CompoundT<void> ClassT;
};

成员类型BaseT指的是:用于构造模板参数类型T的(直接)类型;而BottomT指的是最终去除指针、引用和数组之后的、用于构造T的原始类型。例如,如果T是int**,那么BaseT将是int*,而BottomT将会是int类型。对于成员指针类型,BaseT将会是成员的类型,而ClassT将会是成员所属的类的类型。例如,如果T是一个类型为int(X::*)()的成员函数指针,那么BaseT将会是函数类型int(),而ClassT的类型则为X。如果T不是成员指针类型,那么ClassT将会是CompoundT<void>(这个选择并不是必须的,也可以使用一个noclass来作为ClassT)。

其中,针对指针和引用的局部特化是相当直接的:

// types/type3.hpp

template <typename T>
class CompoundT<T&>
{
public:
enum { IsPtrT = , IsRefT = , IsArrayT = ,
IsFuncT = , IsPtrMemT = };
typedef T BaseT;
typedef typename CompoundT<T>::BottomT BottomT;
typedef CompoundT<void> ClassT;
}; template <typename T>
class CompoundT<T*>
{
public:
enum { IsPtrT = , IsRefT = , IsArrayT = ,
IsFuncT = , IsPtrMemT = };
typedef T BaseT;
typedef typename CompoundT<T>::BottomT BottomT;
typedef CompoundT<void> ClassT;
};

对于成员指针和数组,我们可能会使用同样的技术来处理。但是,在下面的代码中我们将发现,与基本模板相比,这些局部特化将会涉及到更多的模板参数:

// types/type4.hpp

#include <stddef.h>

template<typename T, size_t N>
class CompoundT<T[N]>
{
public:
enum { IsPtrT = , IsRefT = , IsArrayT = ,
IsFuncT = , IsPtrMemT = };
typedef T BaseT;
typedef typename CompoundT<T>::BottomT BottomT;
typedef CompoundT<void> ClassT;
}; template<typename T>
class CompoundT<T[]>
{
public:
enum { IsPtrT = , IsRefT = , IsArrayT = ,
IsFuncT = , IsPtrMemT = };
typedef T BaseT;
typedef typename CompoundT<T>::BottomT BottomT;
typedef CompoundT<void> ClassT;
}; template<typename T>
class CompoundT<T C::*>
{
public:
enum { IsPtrT = , IsRefT = , IsArrayT = ,
IsFuncT = , IsPtrMemT = };
typedef T BaseT;
typedef typename CompoundT<T>::BottomT BottomT;
typedef C ClassT;
};

19.3 辨别函数类型
书中提供了两种辨识函数类型的方法,这里只介绍第2种:
method2:使用SFINAE原则的解决方案:
一个重载函数模板的后面可以是一些显式模板实参;而且对于某些重载函数类型而言,该实参是有效的,但对于其他的重载函数类型,该实参则可能是无效的。实际上,后面使用重载解析对枚举类型进行辨别的技术也使用到了这种方法。SFINAE原则在这里的主要用处是:(1)找到一种构造,该构造对函数类型是无效的,但是对于其他类型都是有效的;或者完全相反。由于前面我们已经能够辨别出几种类型了,所以我们在此可以不再考虑这些(已经可以辨别的)类型。(2)因此,针对上面这种要求,数组类型就是一种有效的构造;因为数组的元素是不能为void值、引用或者函数的。故而可以编写如下代码:

template <typename T>
class IsFunctionT
{
private:
typedef char One;
typedef struct { char a[]; } Two;
template <typename U> static One test ( ... );
template <typename U> static Two test ( U (*)[] ); // 不理解,下面的IsFunctionT<T>::test<T>(0)怎么匹配?
public:
enum { Yes = sizeof(IsFunctionT<T>::test<T>()) == };
enum { No = !Yes };
};

借助于上面这个模板定义,只有对于那些不能作为数组元素类型的类型,IsFunctionT::Yes才是非零值(即为1)。另外,我们应该知道该方法也有一个不足之处;并非只有函数类型不能作为数组元素类型,引用类型和void类型同样也不能作为数组元素类型。(3)幸运的是,我们可以通过为引用类型提供局部特化,以及为void类型提供显式特化,来解决这个不足:

template <typename T>
class IsFunctionT<T&>
{
public:
enum { Yes = };
enum { No = !Yes };
}; template <>
class IsFunctionT<void>
{
public:
enum { Yes = };
enum { No = !Yes };
}; template <>
class IsFunctionT<void const>
{
public:
enum { Yes = };
enum { No = !Yes };
};

至此,我们可以重新改写基本的CompoundT模板如下:

// types/type6.hpp
template <typename T>
class IsFunctionT
{
private:
typedef char One;
typedef struct { char a[]; } Two;
template <typename U> static One test ( ... );
template <typename U> static Two test ( U (*)[] );
public:
enum { Yes = sizeof(IsFunctionT<T>::test<T>()) == };
enum { No = !Yes };
}; template <typename T>
class IsFunctionT<T&>
{
public:
enum { Yes = };
enum { No = !Yes };
}; template <>
class IsFunctionT<void>
{
public:
enum { Yes = };
enum { No = !Yes };
}; template <>
class IsFunctionT<void const>
{
public:
enum { Yes = };
enum { No = !Yes };
}; // 对于void volatile 和 void const volatile类型也是一样的
... template <typename T>
class CompoundT // 基本模板
{
public:
enum { IsPtrT = , IsRefT = , IsArrayT = ,
IsFuncT = IsFunctionT<T>::Yes, IsPtrMemT = };
typedef T BaseT;
typedef T BottomT;
typedef CompoundT<void> ClassT;
};

19.4 运用重载解析辨别枚举类型
重载解析是一个过程,它会根据函数参数的类型,在多个同名函数中选择出一个合适的函数。接下来我们将看到,即使没有进行实际的函数调用,我们也能够利用重载解析来确定所需要的结果。总之,对于测试某个特殊的隐式转型是否存在的情况,这种(利用重载解析的)方法是相当有用的。在此,我们将要利用从枚举类型到整型的隐式转型:它能够帮助我们分辨枚举类型。
先看实现:

// types/type7.hpp
struct SizeOverOne { char c[]; }; template<typename T,
bool convert_possible = !CompoundT<T>::IsFuncT &&
!CompoundT<T>::IsArrayT>
class ConsumeUDC
{
public:
//在ConsumeUDC模板中已经强制定义了一个到T的自定义转型
operator T() const;
}; // 到函数类型的转型是不允许的
// 如果由基本模板得到的convert_possible为false,则匹配此特化;不转型-->无自定义转型操作
template<typename T>
class ConsumeUDC<T, false>
{
}; // 到void类型的转型是不允许的
template <bool convert_possible>
class ConsumeUDC<void, convert_possible>
{
}; char enum_check(bool);
char enum_check(char);
char enum_check(signed char);
char enum_check(unsigned char);
char enum_check(wchar_t); char enum_check(signed short);
char enum_check(unsigned short);
char enum_check(signed int);
char enum_check(unsigned int);
char enum_check(signed long);
char enum_check(unsigned long); #if LONGLONG_EXISTS
char enum_check(signed long long);
char enum_check(unsigned long long);
#endif // LONGLONG_EXISTS // 避免从float到int的意外转型
char enum_check(float);
char enum_check(double);
char enum_check(long double); SizeOverOne enum_check( ... ); // 捕获剩余所有情况
template<typename T>
class IsEnumT
{
public:
enum {
Yes = IsFundaT<T>::No &&
!CompoundT<T>::IsRefT &&
!CompoundT<T>::IsPtrT &&
!CompoundT<T>::IsPtrMemT &&
sizeof(enum_check(ConsumeUDC<T>())) ==
}
enum { No = !Yes };
};

上面代码的核心在于后面的一个sizeof表达式,它的参数是一个函数调用。也就是说,该sizeof表达式将会返回函数调用返回值的类型的大小;其中,将应用重载解析原则来处理enum_check()调用;但另一方面,我们并不需要函数定义,因为实际上并没有真正调用该函数。在上面的例子中,如果实参可以转型为一个整型,那么enum_check()将返回一个char值,其大小为1。对于其他的所有类型,我们使用了一个省略号函数(即enum_check( ... ) ),然而,根据重载解析原则的优先顺序,省略号函数将会是最后的选择。在此,我们对enum_check()的省略号版本进行了特殊的处理,让它返回一个大小大于一个字节的类型(即SizeOverOne)。

对于函数enum_check的调用实参,我们必须仔细地考虑。首先,我们并不知道T是如何构造的,或许将会调用一个特殊的构造函数。为了解决这个问题,我们可以声明一个返回类型为T的函数,然后通过调用这个函数来创建一个T。由于处于sizeof表达式内部,因此该函数实际上并不需要具有函数定义。事实上,更加巧妙的是:对于一个class类型T,重载解析是有可能选择一个针对整型的enum_check()声明的,但前提是该class必须定义一个到整型的自定义转型(有时也称为UDC)函数。到此,问题已经解决了。因为我们在ConsumeUDC模板中已经强制定义了一个到T的自定义转型,该转型运算符同时也为sizeof运算符生成了一个类型为T的实参。下面我们详细分析下:

(1)最开始的实参是一个临时的ConsumeUDC<T>对象;

(2)如果T是一个基本整型,那么将会借助于(ConsumeUDC的)转型运算符来创建一个enum_check()的匹配,该enum_check()以T为实参;

(3)如果T是一个枚举类型,那么将会借助于(ConsumeUDC的)转型运算符,先把类型转化为T,然后调用(从枚举类型到整型的)类型提升,从而能够匹配一个接收转型参数的enum_check()函数(通常而言是enum_check(int));

(4)如果T是一个class类型,而且已经为该class自定义了一个到整型的转型运算符,那么这个转型运算符将不会被考虑。因为对于以匹配为目的的自定义转型而言,最多只能调用一次;而且在前面已经使用了一个从ConsumeUDC<T>到T的自定义转型,所以也就不允许再次调用自定义转型。也就是说,对enum_check()函数而言,class类型最终还是未能转型为整型。

(5)如果最终还是不能让类型T于整型互相匹配,那么将会选择enum_check()函数的省略号版本。

最后,由于我们这里只是为了辨别枚举类型,而不是基本类型或者指针类型,所有我们使用了前面已经开放的IsFundaT和CompoundT类型,从而能够排除这些令IsEnumT<T>::Yes成为非零的其他类型,最后使得只有枚举类型的IsEnumT::Yes才等于1。

19.5 辨别class类型
使用排除原理:如果一个类型不是一个基本类型,也不是枚举类型和组合类型,那么该类型就只能是class类型。

template <typename T>
class IsClassT
{
public:
enum {
Yes = IsFundaT<T>::No &&
IsEnumT<T>::No &&
!CompoundT<T>::IsPtrT &&
!CompoundT<T>::IsRefT &&
!CompoundT<T>::IsArrayT &&
!CompoundT<T>::IsPtrMemT &&
!CompoundT<T>::IsFuncT
};
enum { No = !Yes };
};

C++ template —— 类型区分(十一)的更多相关文章

  1. item 1:理解template类型的推导

    本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 一些用户对复杂的系统会忽略它怎么工作,怎么设计的,但是很高兴去知道它完成的一些事.通过这 ...

  2. Solidity类型Uint类型区分?

    1. Solidity中默认 Uint 也就是Uint256, 也就是 无符号 256位整数范围,即 2的 256次方 减一的 10进制范围, 预计大小为: 115792089237316195423 ...

  3. xcode5.1 armv7 armv7s arm64 类型, 区分, 概念等

    官方: https://developer.apple.com/library/ios/documentation/General/Conceptual/CocoaTouch64BitGuide/In ...

  4. Go标准库:深入剖析Go template

    本文只关注Go text/template的底层结构,带上了很详细的图片以及示例帮助理解,有些地方也附带上了源码进行解释.有了本文的解释,对于Go template的语法以及html/template ...

  5. C++泛型编程:template模板

    泛型编程就是以独立于任何特定类型的方式编写代码,而模板是C++泛型编程的基础. 所谓template,是针对“一个或多个尚未明确的类型”所编写的函数或类. 使用template时,可以显示的或隐示的将 ...

  6. SharePoint 2013 图文开发系列之创建内容类型

    SharePoint内容类型,是很有特色的,就好比发布新闻,同在一张列表里,可以由内容类型区分图片新闻.文字新闻等,顾名思义,就是在一张列表里发布不同类型的项目. 1.添加新项目,选择SharePoi ...

  7. 第三章:Javascript类型、值和变量。

    计算机程序的运行需要对值(value)比如数字3.14或者文本"hello world"进行操作,在编程语言中,能够表示并操作的值的类型叫做数据类型(type),编程语言最基本的特 ...

  8. WPF/Silverlight Template使用及总结(转)

    WPF/Silverlight 中的控件都有Style和Template两种属性.前者解释为样式,是用来改变控件原有属性的,比如 Button 控件的(Width,Height,Background ...

  9. [Effective Modern C++] Item 2. Understand auto type deduction - 了解auto类型推断

    条款二 了解auto类型推断 基础知识 除了一处例外,auto的类型推断与template一样.存在一个直接的从template类型推断到auto类型推断的映射 三类情况下的推断如下所示: // ca ...

随机推荐

  1. SAP NUMBER RANGE维护配置object FBN1 Deletion only possible if status is initial

    背景: 错误日志: SAP FBN1 Deletion only possible if status is initial 场景: 如果目标机已有NUMBER RANGE 不为0,需要删除配置年为9 ...

  2. Java如何检查一个线程停止或没有?

    Java如何检查一个线程停止或没有? 解决方法 下面的示例演示如何使用 isAlive()方法检查一个线程是否停止. public class Main { public static void ma ...

  3. HTTP 错误 500.19 配置文件错误 ( 0x8007000d,0x80070032)

    HTTP 错误 500.19 - Internal Server Error无法访问请求的页面,因为该页的相关配置数据无效. 详细错误信息模块 IIS Web Core 通知 未知 处理程序 尚未确定 ...

  4. 【转】C# 异常处理 throw和throw ex的区别 try catch finally的执行顺序(return)

    [转]throw和throw ex的区别 之前,在使用异常捕获语句try...catch...throw语句时,一直没太留意几种用法的区别,前几天调试程序时无意中了解到几种使用方法是有区别的,网上一查 ...

  5. MySQL存储引擎Innodb和MyISAM对比总结

    Innodb引擎 InnoDB是一个事务型的存储引擎,设计目标是处理大数量数据时提供高性能的服务,它在运行时会在内存中建立缓冲池,用于缓冲数据和索引. Innodb引擎优点 1.支持事务处理.ACID ...

  6. spring 配置 Java配置类装配bean

    https://www.cnblogs.com/chenbenbuyi/p/8457700.html 自动化装配的确有很大的便利性,但是却并不能适用在所有的应用场景,比如需要装配的组件类不是由自己的应 ...

  7. Express框架Fetch通信

    最近自己弄个博客站点,前台用的React,服务器用的是node实现的,node是第一次接触,所以还在摸索,这篇mark下通信时遇到的坑. fetch配置: window.fetchUtility = ...

  8. jiffies存放

    固然书本上讲明jiffies是jiffies_64的低32位,但是我还是自己测试了下,重点在于链接脚本的写法. 此处只是为了测试,因此简化链接脚本. /* link.lds */ 1 ENTRY(_s ...

  9. [转]jmeter实战

    [转]http://blog.csdn.net/ultrani/article/details/8309932 本文主要介绍性能测试中的常用工具jmeter的使用方式,以方便开发人员在自测过程中就能自 ...

  10. koa2使用阿里云oss的nodejs sdk实现上传图片

    nodejs实现上传图片到阿里云,自然是写成接口形式比较方便,前端监听input file的改变,把file对象传入到formData中传入后端,不能直接传入file对象,后端需要接受formData ...