type_traits提供了丰富的编译期间计算、查询、判断、转换和选择的帮助类,其被定义在#include
作用:
- 增强了泛型编程能力;
- 增强程序的弹性,使得在编译期间就可以做到优化、改进甚至排错,提高代码质量;
- 它所提供的选择功能一定程度上可以消除冗长的if-else或switch-case语句,降低程序的圈复杂度,提高代码的可维护性;
- 它所提供的判断功能,在编译期间可以检查出是否是正确的类型,以便能编写更为安全的代码。
作用: 帮助创建编译时常量的标准类。
在C++11之前我们定义编译期常量的方法:
1. 通过定义静态变量,并通过CompileTimeContents::value方式获取该常量。
template
struct CompileTimeContents{
static const int value = 1;
};
2. 通过定义枚举变量方式。
template
struct CompileTimeContents{
enum {value = 1};
};
但在C++11中可以通过std::integral_constant 模版类派生:
template
struct CompileTimeContents :std::integral_constant
{
};
通过CompileTimeContents
在标准库中的定义:
template
struct integral_constant {
static constexpr T value = v;
typedef T value_type;
typedef integral_constant type;
constexpr operator T() { return v; }
};
在traits中判断类型的模板类 都 继承自 std::integral_constant。
1. 判断目标类型是否为期望类型。
traits判断类型(下表展示部分,其他可到 http://www.cplusplus.com/reference/type_traits/ 查看):
traits类型 | 说明 |
is_array |
判断是否为数组类型 |
is_class |
判断是否为类类型而不是union类型 |
is_function |
判断是否为函数类型 |
is_reference |
判断是否为引用类型 |
is_pod |
判断是否为POD(传统Cstruct类型) |
is_trivial |
判断是否为内置类型 |
使用实例:
// is_array example
#include
#include
#include
#include
int main() {
std::cout << std::boolalpha;
std::cout << "is_array:" << std::endl;
std::cout << "int: " << std::is_array::value << std::endl;
std::cout << "int[3]: " << std::is_array::value << std::endl;
std::cout << "array: " << std::is_array>::value << std::endl;
std::cout << "string: " << std::is_array::value << std::endl;
std::cout << "string[3]: " << std::is_array::value << std::endl;
return 0;
}
运行结果:
判断类型通常会与std::enable_if结合使用,通过SFINAE(替换非错误)特性来实现功能更强的重载。
2. 判断两个模板类型之间的关系。
traits类型 | 说明 |
is_same | 判断两个类是否相同 |
is_base_of | 判断Base类型是否为Derivedl 类型的基类 |
is_convertible | 判断前面模板参数类型能否转换为后面模板参数类型 |
具体用法在官网依然有示例:
// is_base_of example
#include
#include
struct A {};
struct B : A {};
int main() {
std::cout << std::boolalpha;
std::cout << "is_base_of:" << std::endl;
std::cout << "int, int: " << std::is_base_of::value << std::endl;
std::cout << "A, A: " << std::is_base_of::value << std::endl;
std::cout << "A, B: " << std::is_base_of::value << std::endl;
std::cout << "A, const B: " << std::is_base_of::value << std::endl;
std::cout << "A&, B&: " << std::is_base_of::value << std::endl;
std::cout << "B, A: " << std::is_base_of::value << std::endl;
return 0;
}
运行结果:
traits中给出了对参数属性的修改接口,如:其cv属性额添加或移除、引用的添加或移除、数组维度的修改等。
traits类型 | 说明 |
remove_cv | 移除cv属性 |
add_cv | 添加cv属性 |
remove_reference | 移除引用 |
add_lvaue_reference | 添加左值引用 |
remove_extent | 移除数组顶层维度 |
示例:
// remove_cv example
#include
#include
int main() {
typedef const volatile char cvchar;
std::remove_cv::type a; // char a
std::remove_cv::type b; // char* b
std::remove_cv::type c; // const char* c (no changes)
if (std::is_const::value)
std::cout << "type of a is const" << std::endl;
else
std::cout << "type of a is not const" << std::endl;
if (std::is_volatile::value)
std::cout << "type of a is volatile" << std::endl;
else
std::cout << "type of a is not volatile" << std::endl;
return 0;
}
运行结果:
根据条件选择traits
std::conditional 在编译期间根据判断式选择两个类型中的一个,类似于常用的条件表达式。
template struct conditional;
当条件为真返回T类型,条件为假返回F类型。
例如:比较long long类型与long double 返回较大的类型
#include
#include
#include
int main() {
typedef std::conditional<(sizeof(long long) > sizeof(long double)), \
long long, long double > ::type max_size_t;
std::cout << typeid(max_size_t).name() << std::endl;
return 0;
}
运行结果:
通常我们获取函数返回值的类型使用的是decltype方式:
template
auto Func(T a, Arg arg)->decltype(f(arg))
{
return f(arg);
}
但是在某个类型没有模板参数时,就不能通过decltype来获取类型
#include
#include
class A{
A() = delete;
public:
int operator()(int i){
return i;
}
};
int main() {
// decltype(A()(0)) i = 4;
decltype(std::declval()(std::declval())) i = 4;
std::cout << typeid(decltype(std::declval()(std::declval()))).name() << std::endl;
std::cout << i << std::endl;
return 0;
}
当用仅用decltype方式获取类型时,由于A没有默认构造函数,代码出错。
之后采用declval方式,通过 declval() 获取任意类型的临时值,但临时值不能用于求值,需要借助decltype对临时值进行类型推导,得出最终返回值。
在traits中提供了另一解决途径:std::result_of
std::result_of::type i = 4;
函数原型:
// 第一个模板参数为可调用对象类型,第二个模板参数为参数类型
template
struct result_of;
编译器在匹配重载函数时,通常匹配所有的重载函数,匹配一个如果失败了,编译器不会报错,而是接着匹配其他重载函数,选择最精确的一个去执行,整个过程不会报错(SFINAE)。
tratis中std::enable_if 根据限定条件选择重载函数,只对满足条件的函数有效。可作用于返回值、模板定义、类模板特化、参数类型限定。
函数原型:
template
struct enable_if;
用法示例:
#include
#include
// 1. 对函数返回值限定,只有模板参数T是integral类型时,才执行此函数
template
typename std::enable_if::value, bool>::type
is_odd(T i) { return bool(i % 2); }
// 2. 对模板参数限定,模板特化时,模板参数只能是intergal类型
template < class T,
class = typename std::enable_if::value>::type>
bool is_even(T i) { return !bool(i % 2); }
int main() {
short int i = 1; // code does not compile if type of i is not integral
std::cout << std::boolalpha;
std::cout << "i is odd: " << is_odd(i) << std::endl;
std::cout << "i is even: " << is_even(i) << std::endl;
return 0;
}
且通过编译期检查输入模板参数是否有效,来提前显示编译错误,避免运行时才被发现。