一、类型萃取
type_traits 实现了在编译期计算、查询、判断、转换和选择类型的能力,使得我们在编译期就能做到优化改进和排错。
实现如下:
template <class T, T v>
struct integral_constant {
static const T value = v;
typedef T value_type;
typedef integral_constant<T,v> type;
constexpr operator value_type() { return v; }
};
typedef integral_constant<bool, true> true_type;
typedef integral_constant<bool, false> false_type;
true_type和false_type是integral_constant的两个实例, 定义了编译期的true和false类型。type_traits的类型判断就是通过继承这两个实例而实现的。
type_traits中的判断类型的接口有很多,在这里只看几个接口,其他的接口也是相同的道理。
1、判断是否是const修饰的类型
template<class T> struct is_const;
下面通过一个例子来看看他的效果。
#include <iostream>
#include <type_traits>
using namespace std;
int main()
{
cout << "is_const:" << endl;
cout << "int:" << is_const<int>::value << endl;
cout << "const int:" << is_const<const int>::value << endl;
cout << "int*:" << is_const<int*>::value << endl;
cout << "const int*:" << is_const<const int*>::value << endl;
cout << "int* const:" << is_const<int* const>::value << endl;
cout << "float:" << is_const<float>::value << endl;
cout << "const float:" << is_const<const float>::value << endl;
return 0;
}
输出如下:
is_const:
int:0
const int:1
int*:0
const int*:0
int* const:1
float:0
const float:1
大部分情况能正常判断出来,但是const int*为什么没有判断出来呢?
const int* 中的const是修饰的指定所指向的内容,表示指针的内容不能修改,但是由他定义的指针变量是可以修改的。
由此推测is_const只是判断由形参类型定义的变量。
下面来看来他是如何实现的:
#include <iostream>
#include <type_traits>
using namespace std;
template <class T>
struct my_is_const : std::false_type
{};
template <class T>
struct my_is_const<T const> : std::true_type
{};
int main()
{
cout << "is_const:" << endl;
cout << "int:" << my_is_const<int>::value << endl;
cout << "const int:" << my_is_const<const int>::value << endl;
cout << "int*:" << my_is_const<int*>::value << endl;
cout << "const int*:" << my_is_const<const int*>::value << endl;
cout << "int* const:" << my_is_const<int* const>::value << endl;
cout << "float:" << my_is_const<float>::value << endl;
cout << "const float:" << my_is_const<const float>::value << endl;
}
运行这段代码发现其输出是如标准的is_const输出一致,那么is_const的实现就是类型这样的代码。
那么这段代码是如何实现判断类型是否是const修饰的呢?
首先来看这段代码:
template <class T>
struct my_is_const : std::false_type
{};
这段代码很好理解,std::false_type表示的是一个false常量,就是说用这个模板类实例后其值都是false,也就是非const修饰的。
那么什么时候会实例化为true呢,关键在下面的代码:
template <class T>
struct my_is_const<T const> : std::true_type
{};
这段代码用到的模板的特化。
template <class T> struct my_is_const<T const> 在这行代码中,const 就是一个类型范围的特化。
当我们实例my_is_const<const int>时,由于在这里有个const,所以会匹配到特化的版本,这里我们继承的是true类型,所以编译器就会将他实现化true的类型,我们通过true类型的value就能判断出来是否是const修饰的了。
前面我们说过标准的is_const在判断const int*类型时,判断其不是const的,根据上面的原理我们可不可以自己实现判断其是const的呢。代码如下:
#include <iostream>
#include <type_traits>
using namespace std;
template <class T>
struct my_is_const : std::false_type
{};
template <class T>
struct my_is_const<T const> : std::true_type
{};
template <class T>
struct my_is_const<const T> : std::true_type
{
}
int main()
{
cout << "is_const:" << endl;
cout << "int:" << my_is_const<int>::value << endl;
cout << "const int:" << my_is_const<const int>::value << endl;
cout << "int*:" << my_is_const<int*>::value << endl;
cout << "const int*:" << my_is_const<const int*>::value << endl;
cout << "int* const:" << my_is_const<int* const>::value << endl;
cout << "float:" << my_is_const<float>::value << endl;
cout << "const float:" << my_is_const<const float>::value << endl;
}
编译时报错了
my_is_const.cpp:14:8: error: redefinition of ‘struct my_is_const<const T>’
14 | struct my_is_const<const T> : std::true_type
| ^~~~~~~~~~~~~~~~~~~~
my_is_const.cpp:10:8: note: previous definition of ‘struct my_is_const<const T>’
10 | struct my_is_const<T const> : std::true_type
| ^~~~~~~~~~~~~~~~~~~~
my_is_const.cpp:14:1: error: multiple types in one declaration
14 | struct my_is_const<const T> : std::true_type
| ^~~~~~
从报错来看const T和T const 重复定义了。
对于普通类型来说,const放在类型前面和类型后是没有区别的,const只有在定义指针时放在前面和后面才有区别,再试着将代码修改一下:
#include <iostream>
#include <type_traits>
using namespace std;
template <class T>
struct my_is_const : std::false_type
{};
template <class T>
struct my_is_const<T const> : std::true_type
{};
template <class T>
struct my_is_const<const T*> : std::true_type
{};
int main()
{
cout << "is_const:" << endl;
cout << "int:" << my_is_const<int>::value << endl;
cout << "const int:" << my_is_const<const int>::value << endl;
cout << "int*:" << my_is_const<int*>::value << endl;
cout << "const int*:" << my_is_const<const int*>::value << endl;
cout << "int* const:" << my_is_const<int* const>::value << endl;
cout << "float:" << my_is_const<float>::value << endl;
cout << "const float:" << my_is_const<const float>::value << endl;
}
这次编译通过了,而且运行结果也如我们所预期的:
is_const:
int:0
const int:1
int*:0
const int*:1
int* const:1
float:0
const float:1
二、可变参数模板
上面判断的是类型的const,我们能否实现一个判断函数的const 呢?
这里的关键就是模板的特化,我们怎么特化一个函数类型呢?
我们把函数的特征表示出来是否就是能特化一个函数类型呢?下面就来试验下
#include <iostream>
#include <type_traits>
using namespace std;
template <class R>
struct func_is_const : std::false_type
{};
template <class R>
struct func_is_const<const R(*)()> : std::true_type
{};
const int func();
char func2();
typedef const int (*func_type)();
typedef char (*func2_type)();
int main()
{
cout << "const int func():" << func_is_const<func_type>::value << endl;
cout << "char func2():" << func_is_const<func2_type>::value << endl;
return 0;
}
在这段代码中,我们将模板特例化函数类型,在使用时将函数类型作为实参传入。运行发现,能正常工作,输出也如预期一样
const int func():1
char func2():0
在这里我们发现,在传入模板实参时,传入的是重命名的函数类型。这样比较麻烦,每一个函数我们都要重命名。
在C++11中增加了一个新的关键字 decltype, 用来在编译时推导出一个表达式的类型。我们用这个关键字来试着求函数的类型。
#include <iostream>
#include <type_traits>
using namespace std;
template <class R>
struct func_is_const : std::false_type
{};
template <class R>
struct func_is_const<const R(*)()> : std::true_type
{};
const int func();
char func2();
int main()
{
cout << "const int func():" << func_is_const<decltype(func)>::value << endl;
cout << "char func2():" << func_is_const<decltype(func2)>::value << endl;
return 0;
}
在运行这段代码时,发现其输出和我们的预期不一样:
const int func():0
char func2():0
这里的const int func()判定为false了,怎么会这样呢?
decltype能推导出一个表达式或变量的类型,那么函数名是否可以当成一个变量。变量都是有地址的,也就是说函数是否是一个地址。
函数名并不是函数地址的代表,这种误解与数组名就是指针一样犯了相同的错误。
函数名是函数实体的代表,不是地址的代表
在网上找到这样一段话,这里说函数名不是地址,那么也就不能用decltype直接推导类型了。
我们用函数名取地址试试,这次发现是运行正常的。
函数由返回值和参数类型和参数个数组成,其中一个元素不一样,其函数的类型也就不一样,按照我们上面这样写,那不是要为每一个函数物例化一个模板类。
能否使用参数模板包含所有的函数类型呢?下面来试试
#include <iostream>
#include <type_traits>
using namespace std;
template <class R>
struct func_is_const : std::false_type
{};
template <class R>
struct func_is_const<const R(*)()> : std::true_type
{};
template <class R, class ...T>
struct func_is_const<const R(*)(T...)> : std::true_type
{};
const int func();
char func2();
const int func3(int);
int func4(int);
const char func5(int, char);
char func6(int, char);
int main()
{
cout << "const int func():" << func_is_const<decltype(&func)>::value << endl;
cout << "char func2():" << func_is_const<decltype(&func2)>::value << endl;
cout << "const int func3(int):" << func_is_const<decltype(&func3)>::value << endl;
cout << "int func4(int):" << func_is_const<decltype(&func4)>::value << endl;
cout << "const char func5(int, char):" << func_is_const<decltype(&func5)>::value << endl;
cout << "char func7(int, char):" << func_is_const<decltype(&func6)>::value << endl;
return 0;
}
运行结果如预期一样,能匹配不同的参数个数和类型。
这里用到了模板特化和类型自动推导。
当我们在特化模板struct func_is_const<const R(*)(T...)>时,实际上我们在这里指定的模板参数的组织形式,当我们传入一个特定的函数类型时,编译器会根据实际的函数类型自动推导出模板参数中R T所表示的实际类型。当我们传入的函数类型没有匹配到特化的模板时,就会匹配到无特化的模板。