title: type_traits源码分析(一)
date: 2022-09-26 11:05:50
tags:
- Modern C++
- C++
- C++ Library
type_traits概述
tyep_traits是C++TMP中不可缺少的一部分,也是Modern C++不可缺少的一部分。任何一位C++程序员,如果想学习Modern C++,想学号Modern C++,肯定不能避免学习type_traits。
什么是type_traits?
type_traits,顾名思义,为类型萃取器/类型特征。type_traits是一系列的元函数。在普通函数中,我们传入数据,返回数据。但是在元函数中,我们传入类型/常量,返回类型/常量。
C++ type_traits的实现,也就是元函数的一般形式, 使用结构体+模板(模式匹配+模板偏特化)的方式。
使用<>来传入类型(语法层面的), 使用::value/::type来获得返回值/返回类型(约定俗成的)。
例如std::true_type::value == true
,std::false_type::value == false
,std::remove_const<const int>::value
所有的type_traits操作均发生在编译时期。
type_trait能够干什么?
type_traits不仅是标准库的重要组成部分,也是模板元编程中的基本技能。
标准库中的类型安全也多亏了type_traits,在各种C++库中也总是能看到type_traits的身影。
type_traits中的约定
type_traits发展了很长的时间,C++标准演化出来了一套规定
通用的规定
- 使用::value来表示返回值(值元函数)
- 使用::type来表示返回类型(类型元函数)
- 每个元函数只能返回一个类型或者一个值
- 元函数可以同时是值元函数和类型元函数
- 元函数必须是一个模板类
- 对于一个type_traits,形如XXX_t的形式是一个类型应用XXX后的返回类型
- 对于一个type_traits,形如XXX_v的形式是一个类型应用XXX后的返回值(C++17)
对于一元type_traits的规定
- 接受一个类型参数 + 可选的辅助附加参数
- 必须可以默认构造
- 必须可以拷贝构造
- 必须公开且无歧义
- 所有的一元元函数都必须从std::integral_constant继承
- 基本的特征成员不应该被隐藏,而且明确可用
对于二元元函数的规定
- 接受有两个类型参数 + 可选的辅助附加参数
- 必须可以默认构造
- 必须可以拷贝构造
- 必须公开且无歧义
- 所有的一元元函数都必须从std::integral_constant的特例化继承
- 基本的特征成员不应该被隐藏,而且明确可用
对于transformation-traits的规定
- 接受一个类型参数 + 可选的辅助附加参数
- 定义一个public的名为type的嵌套类型
- 没有默认/拷贝构造的要求(可以有,也可以没有)
你会发现标准库的有些地方没有遵守这个标准——那些代码是在这个标准没有成型之前,例如迭代器有关的类型萃取
描述
本篇文章使用gcc编译器,会提到17,20中的type_traits,请确保你所用的编译器支持17,20
对于有些条件编译,不列出源码并且不分析
我会将元函数和type_traits等价起来,如果文中说元函数,你可以联系上下文把其当作type_traits。反之亦然。
实现
标准库对于type_traits的实现,放在了type_traits中
integral_constant
template<typename _Tp, _Tp __v>
struct integral_constant
{
static constexpr _Tp value = __v;
typedef _Tp value_type;
typedef integral_constant<_Tp, __v> type;
constexpr operator value_type() const noexcept { return value; }
//有constexpr性质,可以编译时期求值
constexpr value_type operator()() const noexcept { return value; }
};
我们可以看见integral_constant定义的很简单,记得unix的哲学吗? less is more! 几乎所有的type_traits都会直接的或者间接的继承integral_constant
true_type false_type
true_type
,false_type
仅仅是integral_constant的别名,定义如下
/// The type used as a compile-time boolean with true value.
typedef integral_constant<bool, true> true_type;
/// The type used as a compile-time boolean with false value.
typedef integral_constant<bool, false> false_type;
很多的元函数都继承这两个类型,例如is_XXX等元函数。
bool_constant
很好理解
/// The type used as a compile-time boolean with true value.
typedef integral_constant<bool, true> true_type;
/// The type used as a compile-time boolean with false value.
typedef integral_constant<bool, false> false_type;
conditional
conditional接受三个参数,一个bool值,两个类型参数,作用相当于编译时期的if,可以这样描述
<bool p, T1, T2> p ? T1 : T2
//前置声明
template<bool, typename, typename>
struct conditional;
...
/// Define a member typedef @c type to one of two argument types.
template<bool _Cond, typename _Iftrue, typename _Iffalse> //主模板
struct conditional
{ typedef _Iftrue type; };
// Partial specialization for false.
template<typename _Iftrue, typename _Iffalse> //特化模板
struct conditional<false, _Iftrue, _Iffalse>
{ typedef _Iffalse type; };
__type_identity
关于个元函数,没有明确的规定,可以教type_is,或者其他的东西。每个标准库的实现都有所不同。作用就是给定一个T类型,返回一个T类型,用于被其他元函数继承。
template <typename _Type>
struct __type_identity
{ using type = _Type; };
template<typename _Tp>
using __type_identity_t = typename __type_identity<_Tp>::type;
logic traits
合取,析取,否定 traits。其中conjunction
、disjunction
、negation
在定义了宏__cpp_lib_logical_traits 201510
才启用
__or_
类似or关键字,__or_元函数接受一个参数包,对这些类型的返回值求析取
template<typename...>
struct __or_; //主模板永远不能被匹配匹配到,所以不用定义
template<>
struct __or_<> //如果没有类型,继承true_type
: public false_type
{ };
template<typename _B1>
struct __or_<_B1> //有一个类型继承哪个类型
: public _B1
{ };
template<typename _B1, typename _B2> //如果时两个类型,递归的继承
struct __or_<_B1, _B2>
: public conditional<_B1::value, _B1, _B2>::type
//if _B1::value==true,那么不再判断,继承_B1即可,我们关心的是::value的这个返回值。
//if _B1::value==false, 则继承_B2,此时_B2匹配到接受一个参数的特例化
{ };
template<typename _B1, typename _B2, typename _B3, typename... _Bn> //接受多个类型
struct __or_<_B1, _B2, _B3, _Bn...>
: public conditional<_B1::value, _B1, __or_<_B2, _B3, _Bn...>>::type
//if _B1::value==true同理
//if _B1::value==false, 递归的继承__or_<_B2, _B3, _Bn...>
{ };
//这种语法是C++17添加进来的,也很有效的简化了模板元编程的复杂性
template<typename... _Bn>
inline constexpr bool __or_v = __or_<_Bn...>::value; //__or_v即是类型用用于__or_v后的返回
//__and_v同
__and_
__and_的功能和实现与__or_的功能和实现大同小异
template<typename...>
struct __and_;
template<>
struct __and_<>
: public true_type //这里继承true_type
{ };
template<typename _B1>
struct __and_<_B1>
: public _B1
{ };
template<typename _B1, typename _B2>
struct __and_<_B1, _B2>
: public conditional<_B1::value, _B2, _B1>::type //这里的类型排列相较于__or_是反的
{ };
template<typename _B1, typename _B2, typename _B3, typename... _Bn>
struct __and_<_B1, _B2, _B3, _Bn...>
: public conditional<_B1::value, __and_<_B2, _B3, _Bn...>, _B1>::type //类型排列同上
{ };
template<typename... _Bn>
inline constexpr bool __and_v = __and_<_Bn...>::value;
__not_
__not_对给定类型的::value返回值取反,实现非常的简单
template<typename _Pp>
struct __not_
: public __bool_constant<!bool(_Pp::value)> //这里有一个强转
{ };
conjunction
合取,功能和__or_一样,通过继承其实现。
template<typename... _Bn>
struct conjunction
: __and_<_Bn...>
{ };
template<typename... _Bn>
inline constexpr bool conjunction_v = conjunction<_Bn...>::value;
disjunction
析取,同上
template<typename... _Bn>
struct disjunction
: __or_<_Bn...>
{ };
template<typename... _Bn>
inline constexpr bool disjunction_v = disjunction<_Bn...>::value;
negation
否定,同上
template<typename _Pp>
struct negation
: __not_<_Pp>
{ };
template<typename _Pp>
inline constexpr bool negation_v = negation<_Pp>::value;
is_l/r reference
传入一个类型,返回其是不是一个左值/右值引用类型,如果具有,返回值/类型为 true/true_type, 否则返回false/false_type
/// is_lvalue_reference
template<typename> //主模板继承false_type
struct is_lvalue_reference
: public false_type { };
template<typename _Tp>
struct is_lvalue_reference<_Tp&> //对于左值引用的特例化,继承true_type
: public true_type { };
/// is_rvalue_reference
template<typename> //同上
struct is_rvalue_reference
: public false_type { };
template<typename _Tp> //同上
struct is_rvalue_reference<_Tp&&>
: public true_type { };
is_reference
传入一个类型,返回其是不是一个引用类型,如果具有,返回值/类型为 true/true_type, 否则返回false/false_type
template<typename>
struct is_reference; //声明
template<typename _Tp>
struct is_reference //左值引用/右值引用
: public __or_<is_lvalue_reference<_Tp>, 这里用__or_来实现
is_rvalue_reference<_Tp>>::type
{ };
is_const
传入一个类型,返回其是否具有const性质,如果具有,返回值/类型为 true/true_type, 否则返回false/false_type。
通过普通的模板特化就可以实现
/// is_const
template<typename>
struct is_const
: public false_type { };
template<typename _Tp>
struct is_const<_Tp const> //对于const的特化
: public true_type { };
注意,对于&/&&is_const的返回类型是一个::false_type,就是所std::is_const<const int &>::value==false
类似的,还有const int *
也是一样。因为这样的语义表示的指向的对象是常量性质的,而不是引用/指针本身是常量性质的。
is_volatile
传入一个类型,返回其是否具有volatile性质,如果具有,返回值/类型为 true/true_type, 否则返回false/false_type。
实现方式和is_const大同小异。注意的事项也和is_const是一样的。
template<typename>
struct is_volatile
: public false_type { };
template<typename _Tp>
struct is_volatile<_Tp volatile>
: public true_type { };
is_function
传入一个类型,返回其是不是一个函数类型,如果具有,返回值/类型为 true/true_type, 否则返回false/false_type。
C++还有这样的功能!这是黑魔法吗?
这不是什么黑魔法,但确实够酷。在你惊叹他的时候,应当了解一下原理。
对于一个T类型,如果其是一个函数/&,对其添加const不会发成任何类型上的变化。例如
void fun() { }
template <typename T>
struct add_const {
using type = const T;
};
int main() {
typename add_const<decltype(fun)>::type c;
return 0;
}
编译器会生成这样的代码
void fun()
{
}
template<typename T>
struct add_const
{
using type = const T;
};
/* First instantiated from: insights.cpp:10 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
struct add_const<void ()>
{
using type = void (); //类型上没有发生任何变化
};
#endif
int main()
{
void c();
return 0;
}
基于这个原理,我们可以轻松实现这个元函数。考虑到对于const T &
,const T &&
(T不是一个函数类型) 应用is_const:value==false &/&&继承false_type,主模板添加const性质之后判断得到的类型是不是具有const性质。
template<typename>
struct is_function;
/// is_function
template<typename _Tp>
struct is_function
: public __bool_constant<!is_const<const _Tp>::value> { };
//对引用考虑即可,对指针添加const变为*const
template<typename _Tp>
struct is_function<_Tp&>
: public false_type { };
template<typename _Tp>
struct is_function<_Tp&&>
: public false_type { };