文章目录

  • 一、type_traits是什么
  • 二、type_traits通常用来做什么
  • 三、辅助基类
  • 四、类型相关判断信息获取
    • 4.1 判断基础类型类别
    • 4.1 判断组合类型类别
    • 4.3 判断类型的属性
    • 4.4 判断类型特征
    • 4.5 获取其他特征
    • 4.6 类型关系判断
  • 五、类型转换修改操作
    • 5.1 Const-volatile相关
    • 5.2 引用相关
    • 5.3 指针相关
    • 5.4 符号相关
    • 5.5 数组相关
    • 5.6 其他各种类型的转换

如果你是一个c++模板用户,大概率多多少少都接触过type traits这个概念,直译就是类型萃取,根据名字也能猜到是用于获取类型的,在c++ 11之前,stl就已经用到了相关技术了,比如迭代器使用相关的类型获取,《STL 源码剖析》有详细介绍,有兴趣的可以去看看。c++ 11更是引入了一个专门的头文件<type_traits>用来做type traits的相关事情,本篇就来聊聊type_traits。

一、type_traits是什么

前面已经说到了,type traits技术是用来获得类型的,而c++ 11引入的type_traits,就是围绕这一点来的,核心就是定义了一系列的类模板,使得程序员可以用来在编译期判断类型的属性、对给定类型进行一些操作获得另一种特定类型、判断类型和类型之间的关系等,这在模板编程里是很有用的,而它们的实现用到的核心技术就是编译期的常量和模板的最优匹配机制。
<type_traits>里面的内容可以分为三大类:
(1)辅助基类: std::integral_constant以及两个常用特化true_type和false_type,用于创建编译器常量,也是类型萃取类模板的基类。
(2)类型萃取类模板: 用于以编译期常量的形式获得类型特征,比如某个类型是不是浮点数,某个类型是不是另一个类型的基类等等,大部分都是是与否的判断,但也有获取数组的秩这种比较特殊的。
(3)类型转换类模板: 用于通过执行特定操作从已有类型获得新类型,比如为一个类型添加const、去除volatile等。

二、type_traits通常用来做什么

利用type_traits里的各种模板类,最重要的就是我们可以在编译期获得特定的类型type,或者是特定的常量值value,用它们我们可以实现很多事情,比如:
(1)使用type进行变量的声明,比如std::conditional得到的类型:

//简单展示功能,实际使用种int和double通常都是模板参数相关的类型
typedef std::conditional<sizeof(int) >= sizeof(double), int, double>::type Type;
Type a;
a = 3;

(2)使用type进行模板匹配的选择,最典型的是enable_if来实现SFINAE,后面会详细聊下这个,以下是一个简单的例子:

// 1. the return type (bool) is only valid if T is an integral type:
template <class T>
typename std::enable_if<std::is_integral<T>::value,bool>::typeis_odd (T i) {return bool(i%2);}// 2. the second template argument is only valid if T is an integral type:
template < class T,class = typename std::enable_if<std::is_integral<T>::value>::type>
bool is_even (T i) {return !bool(i%2);}

这里的enable if确保只有整型才能够使用这两个函数模板,其他类型的std::enable_if<std::is_integral::value>::type是不存在的,所以对于这俩模板的实现会是Substitution Failure,但如果还有其他的可以Substitution success的模板也是没有问题的,如果没有就会报编译错误,这就是所谓的SFINAE:Substitution Failure Is Not An Error,也就是存在Substitution Failure不认为是错误,只要还有其他能够成功的。
(2)利用is_xxx所生成的bool类型的编译器常量值来做编译期的条件分支判断,如下:

void algorithm_signed  (int i)      { /*...*/ }
void algorithm_unsigned(unsigned u) { /*...*/ } template <typename T>
void algorithm(T t)
{if constexpr(std::is_signed<T>::value)algorithm_signed(t);elseif constexpr (std::is_unsigned<T>::value)algorithm_unsigned(t);elsestatic_assert(std::is_signed<T>::value || std::is_unsigned<T>::value, "Must be signed or unsigned!");
}

这里的if constexpr是c++ 17后引入的新语法,可以在编译期做分支判断。

三、辅助基类

辅助类指的是std::integral_constant和以它为基础的两个实例化std::true_type 和std::false_type,
典型的定义如下:


template<class T, T v>
struct integral_constant {static constexpr T value = v;using value_type = T;using type = integral_constant; // using injected-class-nameconstexpr operator value_type() const noexcept { return value; }constexpr value_type operator()() const noexcept { return value; } // since c++14
};
using true_type = integral_constant<bool,true>;
using false_type = integral_constant<bool,false>;

std::integral_constant是一个类模板,用于为特定类型封装一个静态常量和对应类型,是整个c++ type traits的基础。
首先我们看模板参数,有两个参数,第一个是类型T,第二个是对应类型的值v,value是一个编译期就能确定的常量,值根据v确定,value_type就是类型T,而using type = integral_constant则是表示type指代实例化后的integral_constant类型。有两个成员函数,均用于返回wrapped value,都是constexpr,可以获得编译期的值:
(1)类型转换函数方式:constexpr operator value_type() const noexcept;
(2)仿函数方式:constexpr value_type operator()() const noexcept; (since C++14)
举一个简单的例子,如果有这样的定义:

    enum class my_e { e1, e2 };typedef std::integral_constant<my_e, my_e::e1> my_e_e1;typedef std::integral_constant<my_e, my_e::e2> my_e_e2;
}

如果我们要获取my_e_e1里的value,有以下几种方式
(1)my_e_e1::value,直接获取。
(2)my_e_e1() ,调用constexpr value_type operator()() const noexcept { return value; } // since c++14
(3)my_e_e1 v; 然后直接通过v 调用,实际上调用的是constexpr operator value_type() const noexcept { return value; }

四、类型相关判断信息获取

4.1 判断基础类型类别

此类模板类用于判断给定的类型是不是某种基础类型,一共有以下几种

以is_array为例,可能的一种实现如下:

template<class T>
struct is_array : std::false_type {};template<class T>
struct is_array<T[]> : std::true_type {};template<class T, std::size_t N>
struct is_array<T[N]> : std::true_type {};

定义了三个模板,名字都是is_array,第一个是最通用的,继承自false_type,使用这个模板产出的类里面有个bool类型的编译期常量value,值为false。第二个和第三个都继承自true_type,并且分别限定只能匹配T[]和T[N]的形式,根据模板匹配的规则,这两种形式会优先匹配到这两个模板上而不是第一个通用的模板上,从而得到值为true的编译器常量value。而其他形式的类型均会被第一个模板匹配得到false。

4.1 判断组合类型类别


这些是用于判断是不是某种组合类型,以is_arithmetic为例,它用于判断是否是数字,也就是是否是整数或者浮点数,可能的的定义如下:

template< class T >
struct is_arithmetic : std::integral_constant<bool,std::is_integral<T>::value ||std::is_floating_point<T>::value> {};

这里用到了两外两个type traits,is_integral和is_floating_point,如果二者或为真,is_arithmetic就继承自value为true的integral_constant,反之value为false,从而得到了一个表明给定类型是否是数字的编译期常量。

4.3 判断类型的属性

这些类模板用于判断类型的属性,有以下:

以is_const为例,可能的实现如下:

template<class T> struct is_const          : std::false_type {};
template<class T> struct is_const<const T> : std::true_type {};

这也是典型的利用模板选择的规则,两个模板,如果用const xxx去调用,会match到第二个,否则会退而求其次match到第一个,从而得到true or false。

4.4 判断类型特征

这类的模板类比较多,是用于判断一种类型是否支持一些特定的操作,比如是否支持移动构造,是否能以某些参数构造等,

以std::is_move_constructible为例,用于判断类型是否能进行移动构造,也就是能接收右值引用进行构造,类有移动构造函数或者拷贝构造函数(在没有对移动构造函数进行delete的情况下可以绑定到右值)的时候均可以,一种可能的实现如下:

template<class T>
struct is_move_constructible :std::is_constructible<T, typename std::add_rvalue_reference<T>::type> {};

利用了另外两种traits,is_constructible是判断类型T是否能以特定类型的参数进行构造,这里这个类型是std::add_rvalue_reference<T>::type,也就是右值,add_rvalue_reference这个会使用引用折叠规则,因此用于调用is_move_constructible的T本身可以是左值也可以是右值。

4.5 获取其他特征

有三个比较特别,并不是is_xxx的判断,如下:

以rank为例,用于获取数组的维度,一种可能的实现如下:

template<class T>
struct rank : public std::integral_constant<std::size_t, 0> {};template<class T>
struct rank<T[]> : public std::integral_constant<std::size_t, rank<T>::value + 1> {};template<class T, std::size_t N>
struct rank<T[N]> : public std::integral_constant<std::size_t, rank<T>::value + 1> {};

这几个模板定义很有意思,对于T[]和T[N]这种形式的类型会优先匹配到下面两个模板,进而递归地调用自身并且每次加一,从而最终得到总的维度,下面贴一个例子:

4.6 类型关系判断


其中最直接的就是判断两个类型是不是一致,可能的实现如下:

template<class T, class U>
struct is_same : std::false_type {};template<class T>
struct is_same<T, T> : std::true_type {};

实现原理仍然是模板的最优匹配,不再赘述。

五、类型转换修改操作

还有一大类是类型转换操作,又可以细分为以下几种:

5.1 Const-volatile相关


const和volatile属性的添加和去除。比如remove_cv和add_cv的可能实现如下:

template< class T > struct remove_cv                   { typedef T type; };
template< class T > struct remove_cv<const T>          { typedef T type; };
template< class T > struct remove_cv<volatile T>       { typedef T type; };
template< class T > struct remove_cv<const volatile T> { typedef T type; };template<class T> struct add_cv { typedef const volatile T type; };

5.2 引用相关


可能的实现如下:

template< class T > struct remove_reference      {typedef T type;};
template< class T > struct remove_reference<T&>  {typedef T type;};
template< class T > struct remove_reference<T&&> {typedef T type;};
namespace detail {template <class T>
struct type_identity { using type = T; }; // or use std::type_identity (since C++20)template <class T> // Note that `cv void&` is a substitution failure
auto try_add_lvalue_reference(int) -> type_identity<T&>;
template <class T> // Handle T = cv void case
auto try_add_lvalue_reference(...) -> type_identity<T>;template <class T>
auto try_add_rvalue_reference(int) -> type_identity<T&&>;
template <class T>
auto try_add_rvalue_reference(...) -> type_identity<T>;} // namespace detailtemplate <class T>
struct add_lvalue_reference : decltype(detail::try_add_lvalue_reference<T>(0)) {};template <class T>
struct add_rvalue_reference : decltype(detail::try_add_rvalue_reference<T>(0)) {};

5.3 指针相关


remove_pointer的可能实现如下:

5.4 符号相关

5.5 数组相关

5.6 其他各种类型的转换


这里简单看下enable_if和conditional。

enable的可能实现如下:

template<bool B, class T = void>
struct enable_if {};template<class T>
struct enable_if<true, T> { typedef T type; };

可以看到,第二个是偏特化版本,从而只有第一个bool类型的模板参数为true的时候类里才有type,也就是std::enable_if<true>::type 存在,但是std::enable_if<false>::type 不存在,从而这个type不但可以当作一般的类型使用,也指导了模板的实例化。
比如用于返回值:

template<class T>
typename std::enable_if<std::is_trivially_default_constructible<T>::value>::type construct(T*)
{std::cout << "default constructing trivially default constructible T\n";
}

conditional的可能实现如下:

template<bool B, class T, class F>
struct conditional { typedef T type; };template<class T, class F>
struct conditional<false, T, F> { typedef F type; };

C++11 类型支持之type traits相关推荐

  1. 泛型技巧系列:类型字典和Type Traits

    注意:未经许可,本系列禁止转载. 本文所介绍的技巧,是我在研究泛型开发不久就发现并成功运用的技巧.这个技巧是突破.NET泛型限制,达到"看上去很美"境界的法宝.当然本方法也存在重大 ...

  2. type traits

    Type Traits,  类型萃取,这个概念涉及到的内容太多.基本常用的萃取方法可以参考 http://en.cppreference.com/w/cpp/types 这里主要记录一下对函数的萃取技 ...

  3. [C++]各编译器对C++11的支持比较

    各编译器对C++11的支持比较 转载请注明出处为KlayGE游戏引擎,本文的永久链接为http://www.klayge.org/?p=2154 在KlayGE首次引入C++11特性之后,我顺便调研了 ...

  4. 一文告诉你哪些map element类型支持就地更新

    年初,我代表团队和人民邮电出版社签订了翻译<Go Fundamentals>[1]一书的合同,本月底便是四分之一进度的交稿时间点,近期闲时我们都在忙着做交叉review. 上周末我revi ...

  5. typescript (TS)进阶篇 --- 内置高阶泛型工具类型(Utility Type)

    第一部分 前置内容 关键字 keyof 索引查询 对应任何类型T,keyof T的结果为该类型上所有公有属性key的联合: interface Eg1 {name: string,readonly a ...

  6. C++ 模板类型萃取技术 traits

    当函数,类或者一些封装的通用算法中的某些部分会因为数据类型不同而导致处理或逻辑不同(而我们又不希望因为数据类型的差异而修改算法本身的封装时),traits会是一种很好的解决方案.(类型测试发生在编译期 ...

  7. 全面理解Python中的类型提示(Type Hints)

    众所周知,Python 是动态类型语言,运行时不需要指定变量类型.这一点是不会改变的,但是2015年9月创始人 Guido van Rossum 在 Python 3.5 引入了一个类型系统,允许开发 ...

  8. 用户自定义类型(User-defined Type)参数的传递

    用户自定义类型(User-defined   Type)参数的传递         用户自定义类型在VB中是一种重要的数据类型,它为编程者提供了很大的灵活性,使开发人员可以根据需要构造自己的数据结构. ...

  9. Apache发布NetBeans 10.0,增强对JDK 11的支持

    Apache软件基金会最近发布了NetBeans 10.0,主要特性包括增强对JDK 11的支持.添加对JUnit 5的支持以及重新集成了PHP.JavaScript和Groovy模块.在路线图上,A ...

最新文章

  1. python使用imbalanced-learn的EditedNearestNeighbours方法进行下采样处理数据不平衡问题
  2. 学习笔记97—matlab 获取矩阵中特定值的坐标
  3. 微博收藏(机器学习代码与工具)(一)
  4. C++类对象在内存中的布局
  5. 现在mfc的现状如何_天玑云客:微信代运营现在什么现状?如何挑选合适的代运营公司?...
  6. 蓝桥杯第五届省赛JAVA真题----七对数字
  7. 吴恩达深度学习 —— 2.13 逻辑回归的向量化
  8. 分治法解决逆序对问题
  9. 3、Linux多线程,线程同步(转)
  10. 【云计算】Kubernetes、Marathon等框架需要解决什么样的问题?
  11. 异常详细信息: System.UnauthorizedAccessException: 对路径“”的访问被拒绝。
  12. leetcode1037 有效的回旋镖(Java练习)
  13. c语言windows停止工作,win7系统一打开便签就提示停止工作的解决方法
  14. 屏幕录像专家V2014(附注册码)
  15. HTML分页打印。Web打印控件,完美解决页面排版、结构复杂,内容、图片、表格跨页断裂,自定义页面设置、页眉、页脚、页码,保持原文CSS等难题
  16. Devops知识技能树(译)
  17. Linux查看系统自启动服务
  18. 2019年电子设计国赛综合测评回顾
  19. dpkg: warning: files list file for package ‘‘ missing; assuming package has no files currently insta
  20. 杀戮空间2游戏开服架设好后怎么查找自己服务器

热门文章

  1. 尚未提交线上版本_开发微信小程序如果显示尚未提交线上版本该怎么办
  2. 【已解决】Permission denial: writing to settings requires:android.permission.WRITE_SECURE_SETTINGS
  3. 【ESP32-CAM】使用aduino-IDE的环境配置和烧录相关问题
  4. Unix/Linux编程:sigaction
  5. TCP连接探测中的Keepalive和心跳包. 关键字: tcp keepalive, 心跳, 保活
  6. 软件测试基础知识整理(适用于面试)
  7. Capsule 核心代码解读
  8. 系统分析师备考经验分享(附上备考方法)
  9. Nessus8.4.0漏洞扫描工具安装及使用
  10. python时间复杂度怎么算_python学习:算法和时间复杂度