- 在C和C++中,我们常见的是 值函数(value function) ,即函数接收的参数是某些值,而且函数的返回结果也是值。
- 至于 类型函数(value function) ,函数接受类型作为实参,其返回结果是一个类型。
类型函数在编译时求值,标准库提供了大量的类型函数,这些函数可以帮助库的实现者及程序员编写代码时充分利用语言、标准库以及其他代码的优势
类型函数是C++编译时求值机制的一部分,它允许程序进行更严格的类型检查以获取更优的性能。我们把这种用法称之为模板元编程
1.类型函数decltype
int a;
decltype(a) b;//该函数返回其实参a的已声明类型,也就是int,因此这一句就等价于 int b;
关于decltype详细的用法: decltype(expression) var
第一步:如果expression本身是没有带括号的标识符,则var的类型和expression的类型一致,包括const等限定符
double x=5.5;
double y=7.9;
double &rx=x;
const double *pd;
decltype(x) w;//变量w是double类型,
decltype(rx) u=y;//u的类型是double &,作为引用的u必须进行初始化
decltype(pd) v;//v的类型是const double *
第二步,如果expression是一个函数调用,则var与函数的返回类型一致
long indeed(int);//indeed函数的返回值类型是long
decltype(indeed(3)) m;//m的类型是long
需要说明的是:
对于indeed(3),并不会去调用indeed,编译器只是通过查看函数原型来获取返回类型
第三步,当expression是本身是带括号的标识符(expression为左值),则var是指向该类型的引用
double xx=4.4;
int x=5;
const int a=3;
decltype((xx)) r2=xx;//r2的类型是double &,相当于double &r2=xx;因此r2作为引用必须初始化
decltype(w)=xx;//w是double类型
decltype((a)) ct=x;//ct的类型是const int&
第四步,当前面的条件都不满足,则var的类型和expression的类型一致
int t=3;
int &j=t;
int *p=&j;
int &k=j;
int &n=j;
decltype(j+6) i1;//i1的类型是int #1
decltype(*p) i2=j; //i2的类型是 int&而非int,因此需要初始化 #2
decltype(&p) i3; // i3的类型是int** #3
decltype(100L) i3;//i4的类型是long #4
decltype(k+n) i4;//i5的类型是int #5
#1:j虽然是引用,但表达式j+6是一个具体值而非引用,因此decltype(j+6)的结果类型是int
#2:要记住,decltype中如果是解引用操作,那么得到的是引用类型.
#3:根据int **q=&q; 很好理解i3的类型是int **
#4:100L的数据类型是long,那么结果显然就是long
#5:注意,k和n虽然都是对j的引用,但k+n不是引用:它是两个int的和,因此类型
为int,这和#1的道理一样
2.Iterator_type和Iterator_category
- 类型函数Iterator_type返回C的 迭代器类型(即C::iterator)
- 类型函数Iterator_category返回Iter对应的标签来指示提供的是哪种迭代器
void test(vector<string>& v,forward_list<int>& lst){
sort(v); //我们希望自己编写的sort算法既能排序vector也能排序单向链表
sort(lst);
}
为此,需要先编写两个辅助函数,它们第三个参数用于区分是用于随机访问迭代器还是前向迭代器
templage <typename Ran>
void sort_helper(Ran beg,Ran end,random_access_iterator_tag){
sort(beg,end);//标准库的sort算法只能接受随机访问迭代器
}
templage <typename Ran>
void sort_helper(Ran beg,Ran end,forward_iterator_tag){
vector<decltype(*beg)> v{beg,end};//使用[beg,end)范围内的元素来初始化容器v
sort(v.begin(),v.end());
copy(v.begin(),v.end(),beg);//把元素拷回列表
}
现在开始编写自己的sort函数,在这函数中要选择调用哪种辅助函数
templage <typename C>
void sort(C& c){
using Iter=Iterator_type<C>; //等价于using Iter=typename C::iterator;
sort_helper(c.begin(),c.end(),Iterator_category<Iter>{});
//Iterator_category<Iter>返回Iter对应的标签
}
举例来说明
vector<int> v{2,4,6};
sort(v);//那么上面的C为vector<int>,Iter即为vector<int>::iterator
// 因此Iterator_category<Iter>{}返回的标签是random_access_iterator_tag
//至此,调用哪一个版本的set_helper就很明显了
3 类型谓词
类型谓词是一种比较简单的类型函数,在<type_traits>中,标准库定义了大量的类型谓词
is_void<X> X是void?
is_integral<X> X是一个整数类型?
is_floating_point<X> X是一个浮点数类型 ?
is_array<X> X是一个内置数组?
is_arithmetic<X> X是算术类型?
....
类型萃取返回一个布尔值,为了访问此值,可使用后缀::value,但是不推荐用这种方法
constexpr bool cb1=Is_arithmetic<int>::value;//不推荐用
constexpr bool cb2=Is_arithmetic<int>();//和上面一样,返回一个bool值,而且是
//一个常量表达式。提倡用这个形式
类型谓词常用的场景如下
template <typenae T>
void f(T& t){
// static_assert(std::is_floating_point<T>::value,"FP type expected"); 不建议使用加后缀的方法
static_assert(std::is_floating_point<T>(),"FP type expected");//OK
}
注1:我们在编译时发现错误,总比在运行时出错要好。静态断言static_assert就能够在对编译时
可知的属性做一些检查一旦发现错误,就以编译器错误消息的形式报告所发现的问题。
static_assert(4<=sizeof(int),"integers are too small");//如果4<=sizeof(int)的结果为false,将
//输出"integers are too small"信息
注2:static_assert(A,S),其中A必须是个常量表达式,当A为false时,会把S作为一条编译器错误信息输出
constexpr int cei=10;//ci1是常量表达式
const int ci=5;//ci是常量表达式
int i=8;//i不是常量表达式
static_assert(i<cei,"csdnsb");//错误,i不是常量表达式,导致i<cei的结果
//也不是常量表达式,毕竟常量表达式的意愿就是在编译时求值
static_assert(ci<cei,"csdnsb");//ok