在第三章中,会有一些新的东西,不同于一二章,TMP开始加速了!你将会看见额外的一些元函数实现手法,你肯定会惊讶它的巧妙,特别是void_t类型。总之,做好准备,这章的难度可不同于以往了!
传入一个类型T和一个参数包Ts,返回其T是不是Ts其中的一个类型,如果是,返回值/类型为 true/true_type, 否则返回false/false_type。
有了这个type_traits,可以更加轻松的实现其他type_traits。
这个type_traits的实现方式也是比较的有意思。我们可以有很多种方式来实现__is_one_of
这个元函数,包括使用模板递归继承的方式来实现。gcc则是采用的参数包模式展开的方式。
template
using __is_one_of = __or_...>; //用is_same这个模式来对参数包展开,相当于每个类型都和_Tp应用于is_same,再用作于__or_,即可获得结果。
入一个类型,返回其是不是,如一个有符号/无符号的整数果是,返回值/类型为 true/true_type, 否则返回false/false_type。
// Check if a type is one of the signed integer types.
template
using __is_signed_integer = __is_one_of<__remove_cv_t<_Tp>, //继承__is_one_of<符合要求的类型参数包>
signed char, signed short, signed int, signed long,
signed long long
#if defined(__GLIBCXX_TYPE_INT_N_0) //条件编译大可忽略
, signed __GLIBCXX_TYPE_INT_N_0
#endif
#if defined(__GLIBCXX_TYPE_INT_N_1)
, signed __GLIBCXX_TYPE_INT_N_1
#endif
#if defined(__GLIBCXX_TYPE_INT_N_2)
, signed __GLIBCXX_TYPE_INT_N_2
#endif
#if defined(__GLIBCXX_TYPE_INT_N_3)
, signed __GLIBCXX_TYPE_INT_N_3
#endif
>;
实现方式大同小异
// Check if a type is one of the unsigned integer types.
template
using __is_unsigned_integer = __is_one_of<__remove_cv_t<_Tp>,
unsigned char, unsigned short, unsigned int, unsigned long,
unsigned long long
#if defined(__GLIBCXX_TYPE_INT_N_0)
, unsigned __GLIBCXX_TYPE_INT_N_0
#endif
#if defined(__GLIBCXX_TYPE_INT_N_1)
, unsigned __GLIBCXX_TYPE_INT_N_1
#endif
#if defined(__GLIBCXX_TYPE_INT_N_2)
, unsigned __GLIBCXX_TYPE_INT_N_2
#endif
#if defined(__GLIBCXX_TYPE_INT_N_3)
, unsigned __GLIBCXX_TYPE_INT_N_3
#endif
>;
传入一个类型,返回其是不是标准定义的整数,如果是,返回值/类型为 true/true_type, 否则返回false/false_type。
这个type_traits是其他type_traits组合起来的一个别名。这也是元函数的一个实现手法。
// Check if a type is one of the signed or unsigned integer types.
template
using __is_standard_integer
= __or_<__is_signed_integer<_Tp>, __is_unsigned_integer<_Tp>>;
void_t,就是一个void的别名,但是带有类型参数。你会说:这不是脱裤子放屁吗?有什么用,我直接写void不好吗?
实际上void_t是为TMP服务的,并不是让你当作返回值/类型声明的。我们可以通过SFINE+void_t这种TMP的手法来实现看似不可能的元函数。你马上就会看到
至于你说为什么叫void_t而不是叫其他类型,那你去问问C++委员会吧,顺便告诉我,我也想知道。
// __void_t (std::void_t for C++11)
template using __void_t = void;
传入一个类型,返回其是不是可引用的,如果是,返回值/类型为 true/true_type, 否则返回false/false_type。
需要注意:一个类型T,如果已经是一个引用,无论是左值还是右值,均不可再添加&,就是说没有(int &) &
这样的语法,即引用的引用。但是在模板中,有引用的折叠,别把这件事情忘了。
拿什么是不可引用的呢?
int () const
这样的函数 // Utility to detect referenceable types ([defns.referenceable]).
template //第二个类型参数用于辅助,不需要有名字。
struct __is_referenceable
: public false_type
{ };
template
struct __is_referenceable<_Tp, __void_t<_Tp&>> //如果是一个不可引用的类型,匹配这个特例化失败,转而匹配主模板
: public true_type
{ };
C++中有一些类型,具有独特的语义。例如 无其他动作的构造。内存布局方式是标准的。无其他动作的拷贝。一个抽象基类,一个被final修饰的类,等等deng’den。想对这些类型实现type_traits不是不行,但是编译器为我们内置这样的功能可能会更好,编译速度也会更加快速。
一个trivial需要具有一下的性质
一个stand_layout的对象,需要满足以下几种性质
ps:这里涉及到C++对象模型,感兴趣的可以自行学习。
pod类型需要满足两个要求
pod类型的一些优势/特点
memmove
,memcpy
等.bss
段,在初始化时直接赋0)ps:还是涉及到C++对象模型,有点头秃,大伙看看就好,毕竟术业有专攻。编写应用层C++/非底层库看看就好。
STL的一些算法为了优化性能就会使用is_pod+标签分派的手法来优化性能。在一些很老的标准库中,你会看见对is_pod的手动实现
简单的说,就可以在用于编译期运算的对象。注意其和字面量的区别。
这些type_traits的共呢个正如他们的名字所言,你唯一搞不懂的,应当是什么是POD类型?什么又是trivially_copyable性质?
/// is_trivial
//普通类型是其存储是连续的普通可复制的,并且仅支持静态默认初始化,无论是否为 cv 限定。它包括标量类型,平凡类和任何此类类型的数组。其实现也是编译器内置的。
template
struct is_trivial
: public integral_constant
{
static_assert(std::__is_complete_or_unbounded(__type_identity<_Tp>{}), //这里的静态断言,我们在前面说过了,不再解释,下同。
"template argument must be a complete class or an unbounded array");
};
// is_trivially_copyable
template
struct is_trivially_copyable //是否具有平凡的拷贝
: public integral_constant
{
static_assert(std::__is_complete_or_unbounded(__type_identity<_Tp>{}),
"template argument must be a complete class or an unbounded array");
};
/// is_standard_layout
template
struct is_standard_layout //是否是一个标准布局
: public integral_constant
{
static_assert(std::__is_complete_or_unbounded(__type_identity<_Tp>{}),
"template argument must be a complete class or an unbounded array");
};
/// is_pod (deprecated in C++20) C++20中已经废弃
// Could use is_standard_layout && is_trivial instead of the builtin.
template
struct
_GLIBCXX20_DEPRECATED("use is_standard_layout && is_trivial instead")
is_pod
: public integral_constant
{
static_assert(std::__is_complete_or_unbounded(__type_identity<_Tp>{}),
"template argument must be a complete class or an unbounded array");
};
/// is_literal_type
//17开始 20弃用
template
struct
_GLIBCXX17_DEPRECATED
is_literal_type //是否是一个字面类型
: public integral_constant
{
static_assert(std::__is_complete_or_unbounded(__type_identity<_Tp>{}),
"template argument must be a complete class or an unbounded array");
};
/// is_empty
template
struct is_empty //是否是一个空类型,即类中为空实现
: public integral_constant
{ };
/// is_polymorphic
template
struct is_polymorphic // 是否是一个多态类
: public integral_constant
{ };
#if __cplusplus >= 201402L
#define __cpp_lib_is_final 201402L
/// is_final
template
struct is_final
: public integral_constant
{ };
#endif
/// is_abstract
template
struct is_abstract
: public integral_constant
{ };
template::value> //默认参数是一个bool值,来判断书不是算数类型
struct __is_signed_helper
: public false_type { };
template
struct __is_signed_helper<_Tp, true> //蕴含着是一个符号类型应当先是算数类型
: public integral_constant //如果是一个有符号类型,那么_Tp(-1) < _Tp(0)得到的结果将会是false,是一个无符号类型,-1,会下溢结果为true。
{ };
/// is_signed
template
struct is_signed
: public __is_signed_helper<_Tp>::type
{ }
/// is_unsigned
template
struct is_unsigned
: public __and_/*也可以不加这个元函数,is_singed中有*/, __not_>> //算数类型的同时对is_singed取反
{ };
C++11中新增了decltype关键字,目的就是解决TMP中的一些问题。decltype会对表达式求值,只会根据表达式来推导类型,这点牢记。我不打算说明decltype的所有细节,你可以查阅资料来了解相关知识。decltype为TMP的实现提供了更多的可能性。decltype+SFINAE也是TMP中的一种很巧妙的手法。
declval是一个函数,只有声明,没有实现,目的很简单,服务于TMP。因此,关于declval的所有操作,均在编译时期发生。
/**
* @brief Utility to simplify expressions used in unevaluated operands
* @ingroup utilities
*/
//提供了两个的重载版本
template
_Up
__declval(int);
template
_Tp
__declval(long);
template
auto declval() noexcept -> decltype(__declval<_Tp>(0)); //尾置返回类型
如果T是数组类型,则提供成员常量等于数组第nth维的元素数。或者T是其他类型,或者数组的第0维数为无界的并且nth=0,则值为0。
你可能会疑惑为什么第0维是无界的时候值为0,第1,2,…维不行吗?别忘了C的语法——要确定数组的列数。
/// extent
template
struct extent
: public integral_constant { }; //T不是数组类型,值为0
template
struct extent<_Tp[_Size], _Uint>
: public integral_constant::value> //递归条件_Uint == 0,在_Uint==0的时候,编译器推断的size正好是nth的长度,实现很巧妙,多看几眼慢慢搞清顺序。
{ };
template
struct extent<_Tp[], _Uint> //如果是一个无界数组
: public integral_constant::value>
{ };
用法:
int main() {
int arr [][3][2] = {};
cout << std::extent::value << endl; //0
cout << std::extent::value << endl; //3
cout << std::extent::value << endl; //5
int arr1[1][2];
cout << std::extent::value << endl; //1
cout << std::extent::value << endl; //2
//decltype(arr)的结果为什么超出我么的预期?gcc出bug了吗?
cout << std::extent::value << endl; //0
cout << std::extent::value << endl; //0
cout << std::extent::value << endl; //0
return 0;
}
其实decltype的推导arr的结果为[0][3][2]
。是不同于直接传递int[][3][5]
的。decltype的推断出一个有界数组,而直接传递arr,arr被当作一个无界数组。
传入一个类型,返回其数组的维数。
同样是使用递归+继承的方式来实现。
/// rank
template
struct rank
: public integral_constant { };
template
struct rank<_Tp[_Size]>
: public integral_constant::value> { };
template
struct rank<_Tp[]>
: public integral_constant::value> { };
本章更加深入的了解了type_traits的更加高阶的实现技巧。但这还只是TMP的冰山一角而已。无论难度如何,总之,还能够接受。介绍了一些维TMP服务的基本元函数,例如declval等,虽然定义很短,但是很复杂也巧妙。
下一章,会更加深入的分析type_traits,前三章分析的type_traits的耦合度很低,很多的tyep_traits都相对独立。而后些章节。那些type_traits都需要很多的元函数做支撑。并且还会有更加复杂的TMP实现手法等着你。
至此,type_traits的源码差不多分析的近900行,还有剩下的2000多行等待我们探索。