前置:Type Traits
type_traits 是 STL的一个头文件,定义了一系列模板类,这些模板类在编译期获取某一参数、某一变量、某一个类等的类型信息,用于静态检查。通过使用 type_traits,可以在编译时就获得关于类型的详细信息,从而可以在不运行程序的情况下进行类型相关的优化和检查。
辅助基类
如std::integral_constant 以及其特化 true_type 和 false_type,这些类用于创建编译器常量,同时也是类型萃取类模板的基类。
类型萃取类模板
用于在编译期以常量的形式获取类型特征。例如,std::is_integral 用于检查一个类型是否为整数类型,std::is_floating_point用于检查一个类型是否为浮点类型,std::is_base_of用于检查一个类型是否是另一个类型的基类等。
类型转换类模板
通过执行特定操作从已有类型获得新类型。例如,std::add_const 用于给类型添加 const 限定符,std::remove_volatile 用于移除类型的 volatile 限定符等。
C++11 给出 type_traits 之前,对于类型处理有很多非常不方便的地方。C++是一种静态类型语言,在编译时,每个变量和表达式都必须有明确的类型。C++ 并没有提供一个直接的机制来获取和操作这些类型信息。在没有 type_traits 的情况下,程序员通常需要手动跟踪和处理类型信息。C++的模板元编程是一种在编译期进行计算和类型操作的技术。然而,由于缺乏 type_traits 这样的工具,模板元编程变得异常复杂和难以维护。程序员需要手动编写大量的模板特化和递归代码,以处理各种类型情况。这不仅增加了开发难度,也降低了代码的可读性和可维护性。在泛型编程中,通常需要编写能够处理多种类型的代码。然而,在没有 type_traits 的情况下,很难在编译期对类型进行严格的检查和约束。这可能导致类型不安全的代码,例如错误将一个浮点数传递给期望整数的函数。此外由于缺乏类型萃取和转换的工具,泛型编程的灵活性也会受到限制。在某些情况下,需要根据类型信息来优化代码的性能。例如,对于某些特定的类型,希望使用特定的算法或数据结构。在没有 type_traits 的情况下,这种优化变得非常困难。我们需要编写复杂的条件编译代码,或者使用运行时类型信息(RTTI)来动态地选择实现。这些方法要么增加了编译的复杂性,要么引入了额外的运行时开销。type_traits 提供一套丰富的工具,使得程序员可以在编译期获取和操作类型信息,从而简化了模板元编程,提高了类型安全性,并使得性能优化变得更加容易。type_traits还为 STL 中的算法和容器提供了类型相关的支持,例如,STL 中的算法通过迭代器存取容器内容,而 type_traits 可以协助算法根据迭代器类型或容器类型选择不同的策略。
type_traits 核心类型特性
std::is_same
std::is_same 是一个模板类,用于检查两个类型是否相同。其定义如下:
cpp
template< class T, class U >
struct is_same;
当T和U指名同一类型(考虑 const 和 volatile 限定)时,is_same<T, U>::value 为 true,否则为 false。
cpp
#include <type_traits>
std::cout<<"int and int are"<<std::is_same<int,int>::value <<'\n';//输出 true
std::cout<<"int and float are"<<std::is_same<int,float>::value <<'\n';//输出 false
std::is_integral
std::is_integral 用于检查一个类型是否为整数类型。
cpp
template<class T>
struct is_integral;
当 T 为整数类型时,is_integral<T>::value 为 true,否则为 false。
cpp
#include <type_traits>
std::cout << "int is " << std::is_integral<int>::value << '\n';
// 输出 true
std::cout << "double is " << std::is_integral<double>::value << '\n';
//输出 false
std::is_floating_point
std::is_floating_point 用于检查一个类型是否为浮点类型。
cpp
template<class T>
struct is_floating_point;
当 T 为 float、double 或 long double(包括任何 cv 限定变体)时,is_floating_point<T>::value 为 true,否则为 false。
cpp
#include <type_traits>
std::cout << "float is " << std::is_floating_point<float>::value << '\n'; // 输出 true
std::cout << "int is " << std::is_floating_point<int>::value << '\n'; // 输出 false
std::is_array
std::is_array 用于检查一个类型是否为数组类型。
cpp
template< class T > struct is_array;
当 T 为数组类型时,is_array<T>::value 为 true,否则为 false。
cpp
#include <type_traits>
std::cout << "int[5] is " << std::is_array<int[5]>::value << '\n';// 输出 true
std::cout << "int is " << std::is_array<int>::value << '\n';//输出 false
std::is_pointer
std::is_pointer 用于检查一个类型是否为指针类型。
cpp
template< class T >
struct is_pointer;
当 T 为指针类型时,is_pointer<T>::value 为 true,否则为 false。
样例:
cpp
#include <type_traits>
std::cout << "int* is"<<std::is_pointer<int*>::value<<'\n';// 输出 true
std::cout << "int is"<<std::is_pointer<int>::value<<'\n';// 输出 false
std::is_reference
std::is_reference用于检查一个类型是否为引用类型。
cpp
template<class T>struct is_reference;
当 T 为引用类型时,is_reference<T>::value 为 true,否则为 false。
cpp
#include <type_traits>
std::cout << "int& is " << std::is_reference<int&>::value << '\n'; // 输出 true
std::cout << "int is " << std::is_reference<int>::value << '\n'; // 输出 false
std::is_void
std::is_void 用于检查一个类型是否为 void 类型。其定义如下:
cpp
template< class T >
struct is_void;
当 T 为 void 类型时,is_void<T>::value 为 true,否则为 false。
cpp
#include <type_traits>
std::cout << "void is " << std::is_void<void>::value << '\n'; // 输出 true
std::cout << "int is " << std::is_void<int>::value << '\n'; // 输出 false
std::is_const
std::is_const 是一个模板类,用于检查一个类型是否为常量类型。
cpp
template<class T>
struct is_const;
当 T 为常量类型时,is_const<T>::value 为 true,否则为 false。
cpp
#include <type_traits>
std::cout << "const int is " << std::is_const<const int>::value << '\n'// 输出 true
std::cout << "int is " << std::is_const<int>::value << '\n'; // 输出 false
std::is_volatile
std::is_volatile 用于检查一个类型是否为volatile类型。
cpp
template< class T >
struct is_volatile;
当 T 为volatile类型时,is_volatile<T>::value为 true,否则为 false。
cpp
#include <type_traits>
std::cout << "volatile int is "<< std::is_volatile<volatile int>::value << '\n'; // 输出 true
std::cout << "int is"<< std::is_volatile<int>::value << '\n'; // 输出 false
除了上面介绍的类型,type_traits 还有如 std::is_signed (检查类型是否有符号)、std::is_unsigned (检查类型是否无符号)、std::is_arithmetic ( 检查类型是否为算术类型(整数或浮点数))等基础核心类型。
使用核心类型特性实现泛型编程中的条件编译
在 C++11 中,可以使用模板特化或者 std::enable_if 来实现类似条件编译的效果。使用 std::is_same 和 std::is_integral 以及 std::enable_if 来实现泛型编程中条件编译的实例:
cpp
#include<type_traits>
// 泛型函数模板,用于非整数类型
template<typename T, typename = void>
void print_type_info
(T value, typename std::enable_if<!std::is_integral<T>::value>::type* = nullptr) {
std::cout << "Value is of a non-integral type." << std::endl;
}
//对 int 类型特化的版本
template<typename T>
void print_type_info(T value, typename std::enable_if<std::is_same<T, int>::value>::type* = nullptr) {
std::cout << "Value is of type int with value: " << value << std::endl;
}
//对整数类型特化的版本,但排除 int
template<typename T>
void print_type_info(T value, typename std::enable_if<std::is_integral<T>::value && !std::is_same<T, int>::value>::type* = nullptr) {
std::cout << "Value is of an integral type (non-int) with value: " << value << std::endl;
}
int main() {
//调用 print_type_info 模板函数,传入不同类型的值
print_type_info(12); // 调用 int 特化的版本
print_type_info(3.14f); // 调用非整数类型特化版本
print_type_info(static_cast<long long>(1234567890123456789)); // 调用整数类型特化的版本(排除 int)
return 0;
}
非特化的版本,它接受非整数类型的参数 T,并打印一个通用消息。对int 类型特化的版本,它只接受 int 类型的参数,并打印 int 类型的特定消息。对整数类型(除了int)特化的版本,它接受任何整数类型(除了 int)的参数,并打印整数类型的特定消息。
std::enable_if 用于在编译时启用或禁用模板函数的不同特化版本。std::enable_if 的第二个模板参数是一个类型,当该类型存在时,模板会被启用;当该类型为 void 时,模板会被禁用。std::is_same<T, int>::value 和 std::is_integral<T>::value 用于在编译时检查类型。
问题背景
C++编程中,使用复杂的类型(如 STL 容器和智能指针)时,经常需要定义类型别名以简化代码。C++98 提供了 typedef,而 C++11 引入了别名声明(using)。
typedef 是 C++98 中定义类型别名的方式。
cpp
typedef complex_type alias_name;
简单类型别名
cpp
typedef std::vector<int> IntVector;
复杂类型别名
cpp
typedef std::unique_ptr<std::unordered_map<std::string, std::string>> UPtrMapSS;
限制:typedef 不能直接用于模板化类型别名。
使用 typedef 定义模板化类型别名
cpp
template<typename T>
struct MyAllocList {
typedef std::list<T, MyAlloc<T>> type;
};
MyAllocList<Widget>::type lw;
在模板内使用类型别名
cpp
template<typename T>
class Widget {
private:
typename MyAllocList<T>::type list; // 需要 typename
};
在模板内使用 typedef 定义的类型别名时,需要在前面加上 typename,因为编译器不能确定 ::type 是否是一个类型。
函数指针的别名
cpp
typedef void (*FP)(int, const std::string&);
C++11 引入的 using 关键字可以定义类型别名。
cpp
using alias_name = complex_type;
支持模板化类型别名(别名模板)。在模板中使用时不需要 typename 和 ::type 后缀。
定义类型别名
简单类型别名
cpp
using IntVector = std::vector<int>;
复杂类型别名
cpp
using UPtrMapSS = std::unique_ptr<std::unordered_map<std::string, std::string>>;
使用 using 定义模板化类型别名
cpp
template<typename T>
using MyAllocList = std::list<T, MyAlloc<T>>;
MyAllocList<Widget> lw;
在模板内使用类型别名
cpp
template<typename T>
class Widget {
private:
MyAllocList<T> list; // 不需要 typename
};
Widget<int> w;
别名声明版本:在模板内使用 using 定义的类型别名时,不需要 typename,因为编译器知道MyAllocList<T> 是一个类型别名。
函数指针的别名:
cpp
using FP = void (*)(int, const std::string&);
Type Traits
使用 typedef 实现,需要 typename 和 ::type 后缀。
cpp
//去除常量性
typedef std::remove_const<const int>::type IntType;
//去除引用
typedef std::remove_reference<int&>::type IntRefRemovedType;
//添加左值引用
typedef std::add_lvalue_reference<int>::type IntLValueRefType;
using提供了别名声明版本,更加简洁。
cpp
// 去除常量性
using IntType = std::remove_const_t<int>;
// 去除引用
using IntRefRemovedType = std::remove_reference_t<int&>;
// 添加左值引用
using IntLValueRefType = std::add_lvalue_reference_t<int>;
优先使用别名声明(using):支持模板化类型别名。在模板内使用时更加简洁,不需要 typename 和 ::type 后缀。