traits
C++的标准库提供了<type_traits>,它定义了一些编译时基于模板类的接口用于查询、修改类型的特征:输入的时类型,输出与该类型相关的属性
通过type_traits技术编译器可以回答一系列问题:它是否为数值类型?是否为函数对象?是不是指针?有没有构造函数?能不能拷贝构造等等
type_traits技术还能对类型进行变换,比如给定的类型任意T,能为这个类型添加const修饰符、添加引用或指针等。而这一切都发生在编译时,过程中没有任何运行时开销
判断类别
谓词命名以is_为前缀,通过访问静态成员常量value得到输出结果
cpp
static_assert(std::is_integral<int>::value);//true
static_assert(! std::is_integral<float>::value);//false
static_assert(std::is_floating_point<double>::value);//true
static_assert(std::is_class<struct Point>::value);//true
static_assert(!std::is_same<int , long>::value);//false
is_integral用来判断给定的类型是否为整数类型,使用尖括号将类型输出给这个trait,通过其成员valu来输出一个bool类型的结果
is_floating_point用来判断给定的类型是否为浮点类型
is_class用来判断给定的类型是否为class、struct定义的类型
is_same用来判断给定的两个类型是否为相同类型
对于一个type traits谓词的结果,标准库约定使用value常量来存储,C++17为其预定义了一系列模板,::value的访问方式能够用_v来代替
cpp
template <typename T>constexpr bool is_integral_v = is_integral<T>::value;
template <typename T>constexpr bool is_class_v = is_class<T>::value;
类型变换
标准库有些typed traits拥有修改类型的能力:基于已有类型应用修改得到新的类型,输出类型可以通过访问type类型成员函数得到结果。
类型修改不会原地修改输入的类型,而是产生新的类型以应用这些修改
cpp
static_assert(is_same_v<typename std::remove_const<const int>::type , int>);
static_assert(is_same_v<typename std::remove_const<int>::type , int>);
static_assert(is_same_v<typename std::add_const<int>::type ,const int>);
static_assert(is_same_v<typename std::add_pointer<int **>::type , int ***>);
static_assert(is_same_v<typename std::decay<int[5][6]>::type , int(*)[6]>);
remove_const将输入的类型移除掉const修饰符并返回新的类型,如果不带const则不变
add_const将输入的类型添加const修饰符
add_pointer为输入的类型添加一级指针
decay语义为退化通过模拟函数或值语义传递时会使所有应用到的函数参数类型退化,若为引用那么应用将会去掉
在C++11引入类型别名using后typename decay<int[5] [6]>::type可变为decay_t<int[5] [6]>
下面是标准库定义的一些类型别名
cpp
template<typename T>using remove_const_t = typename remove_const<T>::type;
template<typename T>using decay_t = typename decay<T>::type;
辅助类
辅助类integral_constant将值与对应的类型包裹起来,从而能够将值转化为类型,也能从类型转换回值,实现值与类型的一一映射关系
这个类的主要作用是作为一个容器,用于存储在编译时就可以确定的值,并且这个值的类型也是在编译期可知的。
cpp
using Two = std::integral_constant<int,2>;
using Four = std::integral_constant<int,4>;
static_assert(Two::value * Two::value == Four::value);
Tow和Four为两个类型,分别对应2,4。使用integral_constant将值转换成类型后通过value静态成员常量从类型中得到值并进行计算
标准库对应布尔类型也提供了bool_constant,实现时仅仅是integral_constant的类型别名
cpp
template<bool v>using bool_constant = integral_constant<bool , v>;
将这两个值映射成类型
cpp
uaing true_type = integral_constant<bool , true>;
uaing false_type = integral_constant<bool , false>;
enable_if
enable_if常出现于SFINAE场景中,通过对模板函数,模板类中的类型进行谓词判断,使得程序能够选择合适的模板函数的重载版本或模板类的特化版本
enable_if可接受两个模板参数,第一个参数为bool类型的值,当条件为真时输出的类型成员type的结果为第二个模板参数,否则没有类型成员type
c
int a;
typename std::enable_if<a == 1,int>::type *a; //当a==1时,condition为true,那么type为int,所以会声明一个int* a;
typename std::enable_if<!(a == 1),double>::type *a; //a !=1时,condition为true,那么type为double,所以会声明一个double* a;
enable_if用于函数模板中,典型应用是作为函数模板的返回类型
cpp
template<typename T>
typename std::enable_if<(sizeof(T) > 2)>::type funceb()
{
//....
}
nmsp2::funceb<int>();//void funceb(){}
nmsp2::funceb<char>();//error:未找到匹配的重载函数,条件不满足
//C++14出了这个等同上面
template<typename T>
std::enable_if_t<(sizeof(T) > 2),T> funceb()
{
T myt = {};
return myt;
}
nmsp2::funceb<int>();//int funceb(){}
//nmsp2::funceb<char>();
traits使用示例
- 类型推断
cpp
#include <iostream>
#include <type_traits>
template<typename T>
void print_type_trait(T value) {
// 使用std::integral_constant作为编译时的常量表达式容器
using TraitType = std::integral_constant<bool, std::is_integral<T>::value>;
// 使用if constexpr语句在编译期决定是否打印信息
if constexpr (TraitType::value) {
std::cout << "The type of the variable is integral: " << typeid(T).name() << '\n';
} else {
std::cout << "The type of the variable is not integral: " << typeid(T).name() << '\n';
}
}
int main() {
int integer = 10;
double floating = 3.14;
print_type_trait(integer); // 输出:"The type of the variable is integral"
print_type_trait(floating); // 输出:"The type of the variable is not integral"
return 0;
}
- 模板函数
cpp
template<typename T>
typename std::enable_if<std::is_arithmetic<T>::value>::type
print_sum(T a, T b) {
std::cout << "Sum of arithmetic types: " << a + b << '\n';
}
template<typename T>
typename std::enable_if<!std::is_arithmetic<T>::value>::type
print_sum(T a, T b) {
std::cout << "Sum operation not supported for non-arithmetic types.\n";
}
struct NonArithmetic {};
int main() {
print_sum(3, 5); // 输出:"Sum of arithmetic types: 8"
print_sum("Hello", "World"); // 不会被实例化,因为std::string不是算术类型
print_sum(NonArithmetic{}, NonArithmetic{}); // 输出:"Sum operation not supported for non-arithmetic types."
return 0;
}