如果你是一个c++模板用户,大概率多多少少都接触过type traits这个概念,直译就是类型萃取,根据名字也能猜到是用于获取类型的,在c++ 11之前,stl就已经用到了相关技术了,比如迭代器使用相关的类型获取,《STL 源码剖析》有详细介绍,有兴趣的可以去看看。c++ 11更是引入了一个专门的头文件
前面已经说到了,type traits技术是用来获得类型的,而c++ 11引入的type_traits,就是围绕这一点来的,核心就是定义了一系列的类模板,使得程序员可以用来在编译期判断类型的属性、对给定类型进行一些操作获得另一种特定类型、判断类型和类型之间的关系等,这在模板编程里是很有用的,而它们的实现用到的核心技术就是编译期的常量和模板的最优匹配机制。
(1)辅助基类: std::integral_constant以及两个常用特化true_type和false_type,用于创建编译器常量,也是类型萃取类模板的基类。
(2)类型萃取类模板: 用于以编译期常量的形式获得类型特征,比如某个类型是不是浮点数,某个类型是不是另一个类型的基类等等,大部分都是是与否的判断,但也有获取数组的秩这种比较特殊的。
(3)类型转换类模板: 用于通过执行特定操作从已有类型获得新类型,比如为一个类型添加const、去除volatile等。
利用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>::type
is_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
(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);
else
if constexpr (std::is_unsigned<T>::value)
algorithm_unsigned(t);
else
static_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-name
constexpr 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; }
此类模板类用于判断给定的类型是不是某种基础类型,一共有以下几种
以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。
这些是用于判断是不是某种组合类型,以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,从而得到了一个表明给定类型是否是数字的编译期常量。
这些类模板用于判断类型的属性,有以下:
以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。
这类的模板类比较多,是用于判断一种类型是否支持一些特定的操作,比如是否支持移动构造,是否能以某些参数构造等,
以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
有三个比较特别,并不是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]这种形式的类型会优先匹配到下面两个模板,进而递归地调用自身并且每次加一,从而最终得到总的维度,下面贴一个例子:
template<class T, class U>
struct is_same : std::false_type {};
template<class T>
struct is_same<T, T> : std::true_type {};
实现原理仍然是模板的最优匹配,不再赘述。
还有一大类是类型转换操作,又可以细分为以下几种:
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; };
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 detail
template <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)) {};
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
比如用于返回值:
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; };