在 C++ 中, Type Traits(类型特性)是一组极其强大的模板类和函数, 它们为我们提供了一种在编译时查询, 修改和操作类型信息的机制. Type Traits 是 C++ 标准库 <type_traits>
头文件的核心内容, 其背后利用了模板元编程(Template Meta Programming)这一高深的技术, 使得程序能够在编译时进行诸如类型检查, 类型转换和类型选择等操作, 进而显著提高代码的安全性, 性能和灵活性.
1. 查询或判断类
- 编译时类型检查: 可以在编译时确定某个类型是否满足特定的条件, 例如是否为整数类型, 是否为指针类型等.
- 类型转换: 在编译时对类型进行转换, 例如移除引用, 添加常量限定符等.
- 类型选择: 根据不同的条件在编译时选择不同的类型.
基础类型判断
这类 Traits 主要用于判断一个类型是否属于某种特定的类别, 并且会返回一个布尔值. 下面是一些常用的基础类型判断:
-
is_integral
: 判断一个类型是否为整型.cppstatic_assert(std::is_integral<int>::value); static_assert(std::is_integral_v<int>); // 上例的简写 static_assert(!std::is_integral_v<double>);
-
is_floating_point
: 判断一个类型是否为浮点类型.cppstatic_assert(std::is_floating_point<float>::value); static_assert(std::is_floating_point_v<float>); static_assert(!std::is_floating_point_v<std::string>);
-
is_pointer
: 判断一个类型是否为指针类型.cppstatic_assert(std::is_pointer<int*>::value); static_assert(std::is_pointer_v<int*>); static_assert(!std::is_pointer_v<int>);
复合类型判断
复合类型判断可以帮助我们判断一个类型是否为基础类型, 对象类型, 复合类型或引用类型等.
-
is_fundamental
: 判断一个类型是否为基础类型(如整型, 浮点型, 布尔型等).cppstatic_assert(std::is_fundamental<int>::value); static_assert(std::is_fundamental<bool>::value); static_assert(std::is_fundamental<double>::value); static_assert(std::is_fundamental<void>::value); struct MyStr {}; static_assert(std::is_fundamental<std::string>::value == false); static_assert(std::is_fundamental<MyStr>::value == false);
-
is_object
: 判断一个类型是否为对象类型(不是函数, 引用或void
类型).cppstatic_assert(std::is_object<std::string>::value);
-
is_compound
: 判断一个类型是否为复合类型(不是基础类型).cppstatic_assert(std::is_compound<std::string>::value);
-
is_reference
: 判断一个类型是否为引用类型.cpp// is_reference static_assert(std::is_reference<int&>::value); static_assert(std::is_reference<int&&>::value); static_assert(std::is_reference<int>::value == false);
类型属性
类型属性判断可以帮助我们了解一个类型是否具有某些特定的属性, 比如是否为常量类型, 易变类型或平凡类型等.
-
is_const
: 判断一个类型是否为常量类型.cppstatic_assert(std::is_const<const int>::value); static_assert(!std::is_const<int>::value);
-
is_volatile
: 判断一个类型是否为易变类型.cppstatic_assert(std::is_volatile<volatile int>::value); static_assert(!std::is_volatile<int>::value);
-
is_trivial
: 判断一个类型是否为平凡类型(默认构造函数, 析构函数和拷贝构造函数都是平凡的).cppstruct TrivialStruct { int x; }; static_assert(std::is_trivial<TrivialStruct>::value); struct NonTrivialStruct { NonTrivialStruct() {} ~NonTrivialStruct() {} int x; }; static_assert(!std::is_trivial<NonTrivialStruct>::value);
查询支持的操作
我们还可以查询一个类型是否支持某些操作, 比如是否可以用指定的参数类型构造, 是否有默认构造函数, 是否支持移动赋值操作等.
-
is_constructable
: 判断一个类型是否可以用指定的参数类型构造.cppstatic_assert(std::is_constructible<std::string, const char*>::value); static_assert(!std::is_constructible<int, std::string>::value);
-
is_default_constructible
: 判断一个类型是否有默认构造函数.cppstruct DC { DC() = default; }; static_assert(std::is_default_constructible<DC>::value); struct NonDC { NonDC(int x) {} }; static_assert(!std::is_default_constructible<NonDC>::value);
-
is_nothrow_constructable
: 判断一个类型的构造函数是否不会抛出异常.cppstruct NoThrow { NoThrow() noexcept {} }; static_assert(std::is_nothrow_constructible<NoThrow>::value); struct MayThrow { MayThrow() {} }; static_assert(!std::is_nothrow_constructible<MayThrow>::value);
-
is_move_assignable
: 判断一个类型是否支持移动赋值操作.cppstruct MS { MS& operator=(MS&&) noexcept { return *this; } }; static_assert(std::is_move_assignable<MS>::value); struct MS1 {}; static_assert(std::is_move_assignable<MS1>::value); struct NonMS { NonMS& operator=(NonMS&&) = delete; }; static_assert(!std::is_move_assignable<NonMS>::value);
-
is_destructible
: 判断一个类型是否有析构函数.cppstruct DestructibleStruct { ~DestructibleStruct() {} }; static_assert(std::is_destructible<DestructibleStruct>::value); struct NonDestructibleStruct { private: ~NonDestructibleStruct() {} }; static_assert(!std::is_destructible<NonDestructibleStruct>::value);
查询类型关系
-
is_same
: 判断两个类型是否相同.cppstatic_assert(std::is_same<int, int>::value); static_assert(!std::is_same<int, double>::value);
-
is_base_of
: 判断一个类型是否是另一个类型的基类.cppclass Base {}; class Derived : public Base {}; static_assert(std::is_base_of<Base, Derived>::value); static_assert(!std::is_base_of<Derived, Base>::value);
-
is_convertible
: 判断一个类型是否可以隐式转换为另一个类型.cppstatic_assert(std::is_convertible<int, double>::value); static_assert(!std::is_convertible<std::string, int>::value);
-
is_invocable
: 判断一个可调用对象是否可以用指定的参数类型调用.cppvoid func(int x) {} static_assert(std::is_invocable<decltype(func), int>::value); static_assert(!std::is_invocable<decltype(func), std::string>::value);
2. 类型修改类
这类 Traits 用于在编译时修改类型, 例如添加或移除引用, 指针, 常量限定符等.
CV 相关
-
remove_cv
: 移除类型的const
和volatile
限定符.cppusing CVType = const volatile int; using NonCVType = std::remove_cv<CVType>::type; static_assert(std::is_same<NonCVType, int>::value);
-
remove_const
: 移除类型的const
限定符.cppusing ConstType = const int; using NonConstType = std::remove_const<ConstType>::type; static_assert(std::is_same<NonConstType, int>::value);
-
add_cv
: 为类型添加const
和volatile
限定符.cppusing NonCVType2 = int; using CVType2 = std::add_cv<NonCVType2>::type; static_assert(std::is_same<CVType2, const volatile int>::value);
-
add_const
: 为类型添加const
限定符.cppusing NonConstType2 = int; using ConstType2 = std::add_const<NonConstType2>::type; static_assert(std::is_same<ConstType2, const int>::value);
-
add_volatile
: 为类型添加volatile
限定符.cppusing NonVolatileType2 = int; using VolatileType2 = std::add_volatile<NonVolatileType2>::type; static_assert(std::is_same<VolatileType2, volatile int>::value);
引用
-
remove_reference
: 移除类型的引用.cppusing RefType = int&; using NonRefType = std::remove_reference<RefType>::type; static_assert(std::is_same<NonRefType, int>::value);
-
add_lvalue_reference
: 为类型添加左值引用.cppusing NonRefType2 = int; using LRefType = std::add_lvalue_reference<NonRefType2>::type; static_assert(std::is_same<LRefType, int&>::value);
-
add_rvalue_reference
: 为类型添加右值引用.cppusing NonRefType3 = int; using RRefType = std::add_rvalue_reference<NonRefType3>::type; static_assert(std::is_same<RRefType, int&&>::value);
指针
-
remove_pointer
: 移除类型的指针.cppusing PointerType = int*; using NonPointerType = std::remove_pointer<PointerType>::type; static_assert(std::is_same<NonPointerType, int>::value);
-
add_pointer
: 为类型添加指针.cppusing NonPointerType2 = int; using PointerType2 = std::add_pointer<NonPointerType2>::type; static_assert(std::is_same<PointerType2, int*>::value);
符号
-
make_signed
: 将无符号类型转换为有符号类型.cppusing UnsignedType = unsigned int; using SignedType = std::make_signed<UnsignedType>::type; static_assert(std::is_same<SignedType, int>::value);
-
make_unsigned
: 将有符号类型转换为无符号类型.cppusing SignedType2 = int; using UnsignedType2 = std::make_unsigned<SignedType2>::type; static_assert(std::is_same<UnsignedType2, unsigned int>::value);
3. 其他类型变换
-
decay
: 应用数组到指针, 函数到指针等转换, 移除引用和 cv 限定符等.cppint arr[10]; using DecayedType = std::decay<decltype(arr)>::type; static_assert(std::is_same<DecayedType, int*>::value);
-
enable_if
: 用于在编译时根据条件启用或禁用模板.cpp#include <iostream> template <typename T, typename = std::enable_if_t<std::is_integral<T>::value>> void func(T t) { std::cout << "Integral type: " << t << std::endl; } int main() { func(10); // func("hello"); // 编译错误 return 0; }
-
conditional
: 根据条件在编译时选择不同的类型.cpp#include <iostream> #include <list> #include <type_traits> #include <vector> // 根据类型是否为整型选择不同的容器类型 template <typename T> using ContainerType = std::conditional_t<std::is_integral<T>::value, std::vector<T>, // 如果 T 是整型, 使用 std::vector std::list<T> // 如果 T 不是整型, 使用 std::list >; int main() { // 整型, 使用 std::vector ContainerType<int> c1 = {1, 2, 3, 4, 5}; static_assert(std::is_same<std::vector<int>, decltype(c1)>::value); // 非整型, 使用 std::list ContainerType<double> c2 = {1.1, 2.2, 3.3, 4.4, 5.5}; static_assert(std::is_same<std::list<double>, decltype(c2)>::value); return 0; }
-
common_type
: 找出多个类型的公共类型.cppusing CommonType = std::common_type<int, unsigned int>::type; static_assert(std::is_same<CommonType, unsigned int>::value);
-
result_of
: 用于推断可调用对象的返回类型(C++17 后被invoke_result
替代).cpp#include <iostream> #include <type_traits> int func(int x) { return x * 2; } int main() { using ResultType = std::result_of<decltype(func)(int)>::type; static_assert(std::is_same<ResultType, int>::value); return 0; }
Type Traits 如何工作
看了上述这些 type traits 之后, 我们不禁想知道它是如何实现的.
翻开对应的代码, 我们可以看到其本质还是模板元编程, 在编译期完成.
模板特化
这里我们以is_integral
为例, 来观察它的实现:
可以看到首先对输入参数做了取消const
和volatile
的操作, 接着传给__is_integral_helper
.
cpp
/// is_integral
template<typename _Tp>
struct is_integral
: public __is_integral_helper<__remove_cv_t<_Tp>>::type
{ };
再看__is_integral_helper
的实现, 编译器完整的实现比较长, 这里只截取其中一部分作为说明.
cpp
template<typename>
struct __is_integral_helper
: public false_type { };
template<>
struct __is_integral_helper<bool>
: public true_type { };
template<>
struct __is_integral_helper<char>
: public true_type { };
template<>
struct __is_integral_helper<int>
: public true_type { };
template<>
struct __is_integral_helper<long>
: public true_type { };
// ... more case
其实可以看到这里用到了模板编程:
__is_integral_helper
默认是false_type
.__is_integral_helper
对bool
,char
,int
,long
等类型做了特化, 设置其值为true_type
, 这样如果输入参数匹配到这些类型就会得到true
.
所以我们看到is_integral
本质是通过模板特化实现的.
条件组合
对于复合类型的查询, 则是通过组合基础类型的判断实现. 以is_fundamental
和is_compound
为例, 前者组合了三种简单类型查询: is_arithmetic
,is_void
, is_null_pointer
; 而后者则是对前者的取反操作.
cpp
template<typename _Tp>
struct is_fundamental
: public __or_<is_arithmetic<_Tp>, is_void<_Tp>, is_null_pointer<_Tp>>::type
{ };
/// is_compound
template<typename _Tp>
struct is_compound : public __bool_constant<!is_fundamental<_Tp>::value> { };
总结
C++ Type Traits 是一种强大的编译时编程工具, 它允许程序员在编译时处理类型信息, 从而提高代码的安全性和性能. 通过合理使用 Type Traits, 可以编写出更加通用, 灵活和高效的模板代码.