理解C++ Type Traits

在 C++ 中, Type Traits(类型特性)是一组极其强大的模板类和函数, 它们为我们提供了一种在编译时查询, 修改和操作类型信息的机制. Type Traits 是 C++ 标准库 <type_traits> 头文件的核心内容, 其背后利用了模板元编程(Template Meta Programming)这一高深的技术, 使得程序能够在编译时进行诸如类型检查, 类型转换和类型选择等操作, 进而显著提高代码的安全性, 性能和灵活性.

1. 查询或判断类

  • 编译时类型检查: 可以在编译时确定某个类型是否满足特定的条件, 例如是否为整数类型, 是否为指针类型等.
  • 类型转换: 在编译时对类型进行转换, 例如移除引用, 添加常量限定符等.
  • 类型选择: 根据不同的条件在编译时选择不同的类型.

基础类型判断

这类 Traits 主要用于判断一个类型是否属于某种特定的类别, 并且会返回一个布尔值. 下面是一些常用的基础类型判断:

  • is_integral: 判断一个类型是否为整型.

    cpp 复制代码
    static_assert(std::is_integral<int>::value);
    static_assert(std::is_integral_v<int>); // 上例的简写
    
    static_assert(!std::is_integral_v<double>);
  • is_floating_point: 判断一个类型是否为浮点类型.

    cpp 复制代码
    static_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: 判断一个类型是否为指针类型.

    cpp 复制代码
    static_assert(std::is_pointer<int*>::value);
    static_assert(std::is_pointer_v<int*>);
    
    static_assert(!std::is_pointer_v<int>);

复合类型判断

复合类型判断可以帮助我们判断一个类型是否为基础类型, 对象类型, 复合类型或引用类型等.

  • is_fundamental: 判断一个类型是否为基础类型(如整型, 浮点型, 布尔型等).

    cpp 复制代码
    static_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 类型).

    cpp 复制代码
    static_assert(std::is_object<std::string>::value);
  • is_compound: 判断一个类型是否为复合类型(不是基础类型).

    cpp 复制代码
    static_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: 判断一个类型是否为常量类型.

    cpp 复制代码
    static_assert(std::is_const<const int>::value);
    static_assert(!std::is_const<int>::value);
  • is_volatile: 判断一个类型是否为易变类型.

    cpp 复制代码
    static_assert(std::is_volatile<volatile int>::value);
    static_assert(!std::is_volatile<int>::value);
  • is_trivial: 判断一个类型是否为平凡类型(默认构造函数, 析构函数和拷贝构造函数都是平凡的).

    cpp 复制代码
    struct 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: 判断一个类型是否可以用指定的参数类型构造.

    cpp 复制代码
    static_assert(std::is_constructible<std::string, const char*>::value);
    static_assert(!std::is_constructible<int, std::string>::value);
  • is_default_constructible: 判断一个类型是否有默认构造函数.

    cpp 复制代码
    struct 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: 判断一个类型的构造函数是否不会抛出异常.

    cpp 复制代码
    struct 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: 判断一个类型是否支持移动赋值操作.

    cpp 复制代码
    struct 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: 判断一个类型是否有析构函数.

    cpp 复制代码
    struct DestructibleStruct {
      ~DestructibleStruct() {}
    };
    static_assert(std::is_destructible<DestructibleStruct>::value);
    
    struct NonDestructibleStruct {
     private:
      ~NonDestructibleStruct() {}
    };
    static_assert(!std::is_destructible<NonDestructibleStruct>::value);

查询类型关系

  • is_same: 判断两个类型是否相同.

    cpp 复制代码
    static_assert(std::is_same<int, int>::value);
    static_assert(!std::is_same<int, double>::value);
  • is_base_of: 判断一个类型是否是另一个类型的基类.

    cpp 复制代码
    class 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: 判断一个类型是否可以隐式转换为另一个类型.

    cpp 复制代码
    static_assert(std::is_convertible<int, double>::value);
    static_assert(!std::is_convertible<std::string, int>::value);
  • is_invocable: 判断一个可调用对象是否可以用指定的参数类型调用.

    cpp 复制代码
    void 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 限定符.

    cpp 复制代码
    using CVType = const volatile int;
    using NonCVType = std::remove_cv<CVType>::type;
    static_assert(std::is_same<NonCVType, int>::value);
  • remove_const: 移除类型的 const 限定符.

    cpp 复制代码
    using ConstType = const int;
    using NonConstType = std::remove_const<ConstType>::type;
    static_assert(std::is_same<NonConstType, int>::value);
  • add_cv: 为类型添加 const 和 volatile 限定符.

    cpp 复制代码
    using NonCVType2 = int;
    using CVType2 = std::add_cv<NonCVType2>::type;
    static_assert(std::is_same<CVType2, const volatile int>::value);
  • add_const: 为类型添加 const 限定符.

    cpp 复制代码
    using NonConstType2 = int;
    using ConstType2 = std::add_const<NonConstType2>::type;
    static_assert(std::is_same<ConstType2, const int>::value);
  • add_volatile: 为类型添加 volatile 限定符.

    cpp 复制代码
    using NonVolatileType2 = int;
    using VolatileType2 = std::add_volatile<NonVolatileType2>::type;
    static_assert(std::is_same<VolatileType2, volatile int>::value);

引用

  • remove_reference: 移除类型的引用.

    cpp 复制代码
    using RefType = int&;
    using NonRefType = std::remove_reference<RefType>::type;
    static_assert(std::is_same<NonRefType, int>::value);
  • add_lvalue_reference: 为类型添加左值引用.

    cpp 复制代码
    using NonRefType2 = int;
    using LRefType = std::add_lvalue_reference<NonRefType2>::type;
    static_assert(std::is_same<LRefType, int&>::value);
  • add_rvalue_reference: 为类型添加右值引用.

    cpp 复制代码
    using NonRefType3 = int;
    using RRefType = std::add_rvalue_reference<NonRefType3>::type;
    static_assert(std::is_same<RRefType, int&&>::value);

指针

  • remove_pointer: 移除类型的指针.

    cpp 复制代码
    using PointerType = int*;
    using NonPointerType = std::remove_pointer<PointerType>::type;
    static_assert(std::is_same<NonPointerType, int>::value);
  • add_pointer: 为类型添加指针.

    cpp 复制代码
    using NonPointerType2 = int;
    using PointerType2 = std::add_pointer<NonPointerType2>::type;
    static_assert(std::is_same<PointerType2, int*>::value);

符号

  • make_signed: 将无符号类型转换为有符号类型.

    cpp 复制代码
    using UnsignedType = unsigned int;
    using SignedType = std::make_signed<UnsignedType>::type;
    static_assert(std::is_same<SignedType, int>::value);
  • make_unsigned: 将有符号类型转换为无符号类型.

    cpp 复制代码
    using SignedType2 = int;
    using UnsignedType2 = std::make_unsigned<SignedType2>::type;
    static_assert(std::is_same<UnsignedType2, unsigned int>::value);

3. 其他类型变换

  • decay: 应用数组到指针, 函数到指针等转换, 移除引用和 cv 限定符等.

    cpp 复制代码
    int 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: 找出多个类型的公共类型.

    cpp 复制代码
    using 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, 可以编写出更加通用, 灵活和高效的模板代码.

参考链接

相关推荐
破茧蝴蝶16 分钟前
Python分布式爬虫完整教程
开发语言·python
狗凯之家源码网35 分钟前
全新 PHP 轻量客服聊天室源码深度评测
开发语言·php·聊天室系统
远离UE41 小时前
UE5 Substrate Thin Film 源码拆析
开发语言·javascript·ue5
keyipatience1 小时前
JavaScript学习(1)书写位置,变量、数据类型、运算符
开发语言·前端·javascript·css·学习
一拳一个呆瓜1 小时前
【STL】文件系统导航
c++·stl
All for pursuit.2 小时前
【动态规划-21】312.戳气球
数据结构·c++·算法·leetcode·动态规划
栈帧之外2 小时前
【C++】STL之set
开发语言·c++
catchadmin3 小时前
免费可商用 PHP 管理后台 CatchAdmin V5.4.1 发布,新增 MCP 支持
开发语言·php
zhanghaha13143 小时前
C++构造函数的一些其他应用(逐词逐句保姆讲解)
c++
jimy14 小时前
c++的 Type Traits (二、更详细版本)
开发语言·c++