C++ type traits 源码分析

1
2
3
4
5
6
7
8
9
10
#ifndef _GLIBCXX_TYPE_TRAITS
#define _GLIBCXX_TYPE_TRAITS 1

#pragma GCC system_header

#if __cplusplus < 201103L
# include <bits/c++0x_warning.h>
#else

#include <bits/c++config.h>

首先定义一个宏_GLIBCXX_TYPE_TRAITS,以便在重复包含的时候不会出问题.

#pragma GCC system_header表示编译器将会像对待系统库那样对待这个文件(也就是这个文件可能不符合标准,但是不要警告).

如果当前版本小于c++11(也就是__cplusplus < 201103L),那么type_traits只会包含一个bits/c++0x_warning.h,这个头文件里有一些警告信息.

bits/c++config.h中定义了一些宏,日后细说.

命名空间

1
2
3
namespace std _GLIBCXX_VISIBILITY(default)
{
_GLIBCXX_BEGIN_NAMESPACE_VERSION

这里出现的两个宏的定义位于上面所说的bits/c++config.h中.

_GLIBCXX_VISIBILITY用于改变命名空间可见性.

如果_GLIBCXX_INLINE_VERSION的值不为0,_GLIBCXX_BEGIN_NAMESPACE_VERSION会在这个位置再定义一个命名空间,用来包住后面的定义.

_GLIBCXX_INLINE_VERSION默认是0的,这时候_GLIBCXX_BEGIN_NAMESPACE_VERSION宏是空的.

前向声明

1
2
3
4
5
template<typename... _Elements>
class tuple;

template<typename _Tp>
class reference_wrapper;

这里前向声明了两个定义在其他头文件里的类,从而不用包含这两个类所在的头文件.

这两个类所在的头文件也包含了type_traits,因此type_traits不能再去包含他们.

integral_constant

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  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; }
#if __cplusplus > 201103L

#define __cpp_lib_integral_constant_callable 201304

constexpr value_type operator()() const noexcept { return value; }
#endif
};

数据成员

这个结构体定义了变量value = __v,这是一个constexpr static修饰的数据成员变量.

在C++17之后,这样constexpr修饰的静态成员变量可以在类定义语句内初始化.

在C++标准文档中有这样一句话

A function or static data member declared with the constexpr or consteval specifier is implicitly an inline function or variable
被constexpr或consteval修饰的函数或则静态数据成员视为隐式地被inline修饰.

也就是这么做效果等同inline.

C++17之后可以给静态数据成员加上inline,使得他可以直接在类定义语句内初始化.

接下来是将_Tp定义为value_type,将自身类型定义为type.

函数成员

operator value_type()

如果将类型integral_constant<_Tp, __v>的对象转换为_Tp类型,那么将会调用operator value_type()函数,返回一个_Tp类型的value.

如果有类似这样的语句

1
int i = int(integral_constant<int,1>())

因为这时候value_typeint,那么这个类将会有一个函数成员operator int().

例如上面的语句会返回int类型的1.

operator()()

如果有一个integral_constant<_Tp, __v>类型的对象a,当我们使用a()时,将会调用operator()().

这里的operator()()直接返回了value.

例如,下面的语句,将会返回一个int类型的1.

1
integral_constant<int, 1>()()

静态成员定义

1
2
template<typename _Tp, _Tp __v>
constexpr _Tp integral_constant<_Tp, __v>::value;

如果C++版本小于17,那么还是需要在类定义语句之外使用这个语句来定义value.

在C++标准中有这样一段话

在类定义中的非内联静态数据成员并未被定义,他可能是除cv void以外的未完成类型

因此我们需要在类的外面定义他.

常用类型定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  /// The type used as a compile-time boolean with true value.
using true_type = integral_constant<bool, true>;

/// The type used as a compile-time boolean with false value.
using false_type = integral_constant<bool, false>;

/// @cond undocumented
/// bool_constant for C++11
template<bool __v>
using __bool_constant = integral_constant<bool, __v>;
/// @endcond

#if __cplusplus >= 201703L
# define __cpp_lib_bool_constant 201505
/// Alias template for compile-time boolean constant types.
/// @since C++17
template<bool __v>
using bool_constant = integral_constant<bool, __v>;
#endif

这里使用using重命名了几个特定类型的integral_constant.

其中false_typetrue_type非常常用,他们是模板元编程的基石之一.

conditional

这里是一个前向定义.

1
2
template<bool, typename, typename>
struct conditional;

他的实现也在同一个文件中.

1
2
3
4
5
6
7
8
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; };

以上代码根据bool _Cond的值,来决定conditional::type.

首先定义一个默认模板类,无论如何都把type定义为_Iftrue.

然后特化这个模板,当_Cond传入false的时候,把type定义为_Iffalse.

上述的conditonal的第一个模板参数_Cond其实很有说法.

如果我们直接传布尔值,或者传一个很简单的表达式,那就浪费了这个精巧的结构.

为此,C++定义了__or_, __and_,__not_结构体,来实现复杂的判断.

__type_identity

1
2
3
4
5
6
template <typename _Type>
struct __type_identity
{ using type = _Type; };

template<typename _Tp>
using __type_identity_t = typename __type_identity<_Tp>::type;

这里定义了一个结构体__type_identity,这个结构体内部只是把模板参数中的_Type命名为type.

然后就是定义一个__type_identity_t,以便使用这个type.

逻辑运算

这几个逻辑运算也是模板元编程中非常关键的一点,他们给模板带来了逻辑.

__or_

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
template<typename...>
struct __or_;

template<>
struct __or_<>
: 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
{ };

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
{ };

__or_<>

当我们不给__or_传模板参数时,他就会直接继承一个false_type.

这时候这个__or_value成员就是false.

也就是说以下代码:

1
2
std::__or_<> empty_or;
std::cout << empty_or.value << std::endl;

会输出0.

__or_<_B1>

__or_接受一个模板参数的时候,他直接继承这个这个模板参数.

也就是如果这个参数继承了true_type,也就是_B1::valuetrue,那么__or_就会继承_B1,这时候value成员就会是true.

同理,如果这个参数继承了false_type,那么__or_就继承_B1之后,value成员就会是false.

这非常符合or的特征,当只接收一个布尔值时,计算结果就是这个布尔值.

__or_<_B1, _B2>

如果_B1::valuetrue,那么conditional<_B1::value, _B1, _B2>::type将会返回_B1.

那么__or_直接继承_B1,短路掉_B2.

这时候value就是true.

否则就继承_B2.

继承_B2之后,__or_value取决于_B2::value.

这和or运算的表现一模一样.

这就相当于_B1 || (_B2).

__or_<_B1, _B2, _B3, _Bn…>

这里将传入的模板参数全部展开,就像这样

1
2
3
4
5
conditional<_B1::value, _B1, __or_<_B2, _B3, _B4>>::type
//展开一次
conditional<_B1::value, _B1, conditional<_B2::value, _B2, __or_<_B3, _B4>>>::type
//再展开一次
conditional<_B1::value, _B1, conditional<_B2::value, _B2, conditional<_B3::type, _B3, _B4>>>::type

这就像是_B1 || (_B2 || (_B3 || _B4)).

一旦有一个符合,value就会是true.

__and_

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
template<typename...>
struct __and_;

template<>
struct __and_<>
: public 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
{ };

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
{ };

不传入模板参数的时候,直接继承true_type.

传入一个模板参数的时候,也是直接继承_B1.

传入两个模板参数的时候,如果_B1false,那么直接继承_B1; 否则继承_B2,这时候__and_的值取决于_B2.

后面的展开也和上面同理,相当于展开成_B1 && (_B2 && (_B3 && ...)).

__not_

1
2
3
4
template<typename _Pp>
struct __not_
: public __bool_constant<!bool(_Pp::value)>
{ };

这里就简单很多直接对传入模板参数的value取反,然后继承一个__bool_constant.

快捷方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#if __cplusplus >= 201703L

/// @cond undocumented
template<typename... _Bn>
inline constexpr bool __or_v = __or_<_Bn...>::value;
template<typename... _Bn>
inline constexpr bool __and_v = __and_<_Bn...>::value;

#define __cpp_lib_logical_traits 201510

template<typename... _Bn>
struct conjunction
: __and_<_Bn...>
{ };

template<typename... _Bn>
struct disjunction
: __or_<_Bn...>
{ };

template<typename _Pp>
struct negation
: __not_<_Pp>
{ };

template<typename... _Bn>
inline constexpr bool conjunction_v = conjunction<_Bn...>::value;

template<typename... _Bn>
inline constexpr bool disjunction_v = disjunction<_Bn...>::value;

template<typename _Pp>
inline constexpr bool negation_v = negation<_Pp>::value;

#endif

这里定义了inline constexpr的变量__or_v__and_v,可以方便地访问value.

目前constexpr模板变量直接写在头文件里没有关系,因为他没有外部链接,但是以后可能会有问题.

加上inline的变量可以定义在头文件里,然后被其他文件包含也不会出问题.

类型判断

类型判断的模板类我打乱顺序来讲,但是这样更加易于理解.

is_reference

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
template<typename>
struct is_lvalue_reference
: public false_type { };

template<typename _Tp>
struct is_lvalue_reference<_Tp&>
: public true_type { };

template<typename>
struct is_rvalue_reference
: public false_type { };

template<typename _Tp>
struct is_rvalue_reference<_Tp&&>
: public true_type { };

template<typename _Tp>
struct is_reference
: public __or_<is_lvalue_reference<_Tp>,
is_rvalue_reference<_Tp>>::type
{ };

这里用来判断_Tp是否引用类型,如果了解模板的匹配就会知道,如果是左值或则右值引用,就会继承true_type.

不必多说,很简单.

is_const

1
2
3
4
5
6
7
8
/// is_const
template<typename>
struct is_const
: public false_type { };

template<typename _Tp>
struct is_const<_Tp const>
: public true_type { };

这也同理,如果带有const就会选择第二个特化.

is_function

1
2
3
4
5
6
7
8
9
10
11
template<typename _Tp>
struct is_function
: public __bool_constant<!is_const<const _Tp>::value> { };

template<typename _Tp>
struct is_function<_Tp&>
: public false_type { };

template<typename _Tp>
struct is_function<_Tp&&>
: public false_type { };

如果在_Tp的前面加上const之后,_Tp的类型不为const,那么这个类型就是一个函数.

这里非常巧妙.

我们知道,在函数前面加上const,他只是变成了一个返回const类型的函数.

is_void

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

template<typename _Tp>
using __remove_cv_t = typename remove_cv<_Tp>::type;

template<typename>
struct __is_void_helper
: public false_type { };

template<>
struct __is_void_helper<void>
: public true_type { };

template<typename _Tp>
struct is_void
: public __is_void_helper<__remove_cv_t<_Tp>>::type
{ };

__remove_cv_t作用是将_Tpconstvolatile修饰符去掉(如果有的话).后面会提到他的实现

然后直接和void类型对比,作出判断.

类型操作

修改const和volatile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
  // Const-volatile modifications.

/// remove_const
template<typename _Tp>
struct remove_const
{ typedef _Tp type; };

template<typename _Tp>
struct remove_const<_Tp const>
{ typedef _Tp type; };

/// remove_volatile
template<typename _Tp>
struct remove_volatile
{ typedef _Tp type; };

template<typename _Tp>
struct remove_volatile<_Tp volatile>
{ typedef _Tp type; };

/// remove_cv
template<typename _Tp>
struct remove_cv
{ using type = _Tp; };

template<typename _Tp>
struct remove_cv<const _Tp>
{ using type = _Tp; };

template<typename _Tp>
struct remove_cv<volatile _Tp>
{ using type = _Tp; };

template<typename _Tp>
struct remove_cv<const volatile _Tp>
{ using type = _Tp; };

/// add_const
template<typename _Tp>
struct add_const
{ typedef _Tp const type; };

/// add_volatile
template<typename _Tp>
struct add_volatile
{ typedef _Tp volatile type; };

/// add_cv
template<typename _Tp>
struct add_cv
{
typedef typename
add_const<typename add_volatile<_Tp>::type>::type type;
};

#if __cplusplus > 201103L

#define __cpp_lib_transformation_trait_aliases 201304

/// Alias template for remove_const
template<typename _Tp>
using remove_const_t = typename remove_const<_Tp>::type;

/// Alias template for remove_volatile
template<typename _Tp>
using remove_volatile_t = typename remove_volatile<_Tp>::type;

/// Alias template for remove_cv
template<typename _Tp>
using remove_cv_t = typename remove_cv<_Tp>::type;

/// Alias template for add_const
template<typename _Tp>
using add_const_t = typename add_const<_Tp>::type;

/// Alias template for add_volatile
template<typename _Tp>
using add_volatile_t = typename add_volatile<_Tp>::type;

/// Alias template for add_cv
template<typename _Tp>
using add_cv_t = typename add_cv<_Tp>::type;
#endif

操作非常简单,接收特定类型之后在模板类内部定义一个自己想用的版本就可以了.

修改引用类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
  /// remove_reference
template<typename _Tp>
struct remove_reference
{ typedef _Tp type; };

template<typename _Tp>
struct remove_reference<_Tp&>
{ typedef _Tp type; };

template<typename _Tp>
struct remove_reference<_Tp&&>
{ typedef _Tp type; };

template<typename _Tp, bool = __is_referenceable<_Tp>::value>
struct __add_lvalue_reference_helper
{ typedef _Tp type; };

template<typename _Tp>
struct __add_lvalue_reference_helper<_Tp, true>
{ typedef _Tp& type; };

/// add_lvalue_reference
template<typename _Tp>
struct add_lvalue_reference
: public __add_lvalue_reference_helper<_Tp>
{ };

template<typename _Tp, bool = __is_referenceable<_Tp>::value>
struct __add_rvalue_reference_helper
{ typedef _Tp type; };

template<typename _Tp>
struct __add_rvalue_reference_helper<_Tp, true>
{ typedef _Tp&& type; };

/// add_rvalue_reference
template<typename _Tp>
struct add_rvalue_reference
: public __add_rvalue_reference_helper<_Tp>
{ };

#if __cplusplus > 201103L
/// Alias template for remove_reference
template<typename _Tp>
using remove_reference_t = typename remove_reference<_Tp>::type;

/// Alias template for add_lvalue_reference
template<typename _Tp>
using add_lvalue_reference_t = typename add_lvalue_reference<_Tp>::type;

/// Alias template for add_rvalue_reference
template<typename _Tp>
using add_rvalue_reference_t = typename add_rvalue_reference<_Tp>::type;
#endif

也是一样.

更多类型判断


C++ type traits 源码分析
https://zzidun.tech/ae6bf35b/
作者
zzidun pavo
发布于
2022年3月5日
许可协议