理解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: 移除类型的 constvolatile 限定符.

    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: 为类型添加 constvolatile 限定符.

    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为例, 来观察它的实现:

可以看到首先对输入参数做了取消constvolatile的操作, 接着传给__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_helperbool,char, int, long等类型做了特化, 设置其值为true_type, 这样如果输入参数匹配到这些类型就会得到 true.

所以我们看到is_integral本质是通过模板特化实现的.

条件组合

对于复合类型的查询, 则是通过组合基础类型的判断实现. 以is_fundamentalis_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, 可以编写出更加通用, 灵活和高效的模板代码.

参考链接

相关推荐
Source.Liu1 分钟前
【CXX-Qt】1.1 Rust中的QObjects
开发语言·qt·rust
zhangj11257 分钟前
Go语言sync包使用指南
开发语言·后端·golang
Hello.Reader9 分钟前
用 TDD 构建 Rust 命令行搜索功能:以 minigrep 为例
开发语言·rust·tdd
水水阿水水19 分钟前
第二章:QT核心机制(二)
服务器·前端·c++·qt
T风呤23 分钟前
图像锐化(QT)
c++
MoonBit月兔1 小时前
双周报Vol.65:新增is表达式、字符串构造和数组模式匹配增强、IDE模式匹配补全增强...多项技术更新!
开发语言·ide·编程语言·moonbit
笔记_blog1 小时前
[MFC] 使用控件
c++·mfc
qq_441685751 小时前
bash shell笔记——循环结构
开发语言·bash
KAI77381 小时前
2月11日QT
开发语言·qt
论迹2 小时前
【JavaEE】-- 多线程(初阶)1
java·开发语言·网络·java-ee