闭关之 C++ Template 笔记(五):Concepts 和 Standard Type Utilitie

目录

前言

   本书第二部分主要深入探讨使用模板的一些原则和规范。如果想接触编译器源代码,该部分内容可以深入去了解。但是作为像我这种图形开发人员来说,可以依赖编译器约束自己开发中行为。所以就不做笔记了。而且,该部分内容通过本书其他部分的学习也能掌握。适合在实际开发中作为阅读材料使用。

   花了一些时间将第二部分和附录部分的内容看完了。还是有必要总结一下附录中 Concepts 和 Standard Type Utilities 这两部分内容!


附录 E Concepts

E.1 使用 Concept

  • 示例
    template <typename T> 
        requires LessThanComparable<T> //concept
    T Max(T a, T b) 
    {
        return b < a ? a : b;
    }
    
  • Concept 是一个 Boolean 谓词,用于评估常量表达式 (constant-expression)
    • 该评估发生在编译器,不会有生成代码的开销
    • 只有 Concept 为 true 时,模板才会被实例化
    • Concept 为 false 时,会报错
  • requires 也可以不使用 concept
    • 可以使用任何 bool 类型的常量表达式
    • 但建议使用 concept,毕竟报错时,提示信息非常友好
      class Person
      {
      private:
          std::string name;
      public:
          template<typename STR>
              requires std::is_convertible_v<STR, std::string>
          explicit Person(STR&& n)
              : name{ std::forward<STR>(n) }
          {
              std::cout << "TMPL-CONSTR for '" << name << "'\n";
      
          }
      };
      
  • Concept 的设计
    • Concept 应该反映代码的含义,而不是它是否可编译
  • 使用多个 requires
    template<typename Seq>
        requires Sequence<Seq> && 
                EqualityComparable<typename Seq::value_type>
    typename Seq::iterator find(Seq const& seq, typename Seq::value_type const& val)
    {
        return std::find(seq.begin(), seq.end(), val);
    }
    
    • 也可是使用 ||
      • 不应随意使用,会对编译资源造成负担
      • 但某些情况下也很实用
      template<typename T>
          requires Integral<T> ||
                  FloatingPoint<T>
      T power(T b, T p);
      
  • 一个 requires 也可以包含多个模板参数
    template<typename T, typename U>
        requires SomeConcept<T, U>
    auto f(T x, U y) -> decltype(x + y);
    
  • 单个 requires 的简写方法
    • 当需要单个 requires, 并且该 requires 只涉及一个参数时,可以简写
      template<LessThanComparable T>
      T max(T a, T b) 
      {
          return b < a ? a : b;
      }
      
  • 需要两个 requires 的简写示例
    template<Sequence Seq>
        requires EqualityComparable<typename Seq::value_type>
    typename Seq::iterator find(Seq const& seq, typename Seq::value_type const& val)
    {
        return std::find(seq.begin(), seq.end(), val);
    }
    

E.2 定义 Concept

  • Concept 类似 bool 类型的 constexpr 变量模板
  • 形式
    • template<typename T> concept LessThanComparable = ...;
  • ... 可以用一个表达式来代替
    • 使用各种 traits 来确定类型 T 是否确实可以使用 < 操作符进行比较
    • requires 表达式
  • Concept 定义示例
    template<typename T>
    concept LessThanComparable = requires(T x, T y)
    {
        // {x < y} 必须是 SFINAE 有效的
        // std::convertible_to<bool> 返回值类型必须可以被转换成 bool
        {x < y} -> std::convertible_to<bool>;
    };
    
    • requires 中的参数(形参)
      • 这些参数永远不会被实参替换,而是被视为一组 “虚拟变量”
      • 用于表示 requiress 表达式主体中的需求
    • 可以在 -> 标记之前插入关键字 noexcept,以表示大括号中的表达式不应该抛出异常
  • 如果不需要约束返回值类型,可以省略掉 -> type
    template<typename T>
    concept Swappable = requires(T x, T y)
    {
        swap(x, y);
    };
    
  • requires 表达式也可以表达对关联类型的需求
    template<typename Seq>
    concept Sequence = requires(Seq seq)
    {
        // typename type; 表示需求:类型存在。也称类型需求(type requirement)
        typename Seq::iterator;
        { seq.begin() } -> std::convertible_to<Seq::iterator>;
        //...
    };
    
  • requires 表达式也可以使用另一个 concept 约束行为
    template<typename Seq>
    concept Sequence = requires(Seq seq)
    {
        typename Seq::iterator;
        requires Iterator<typename Seq::iterator>;
        { seq.begin() } -> std::convertible_to<Seq::iterator>;
        //...
    };
    

    E.3 约束上的重载 (Overloading on Constraints)

    • 如果一个模板使用了两个不同的 requires,这两个模板就是不同的模板。重载时,就选择满足Concept所约束的那个模板。

    E.3.1 约束包含 (Constraint Subsumption)

    • 用 requires 约束区分的重载函数模板,涉及的约束(requires)通常是互斥的
      • 尽量避免某种类型同时满足重载函数模板中不同的 requires
    • 然而,有些 concept 从来都不是互斥的,而是一个“包含另一个”
      • 如,标准库中的 iterator
      //C++17
      template<typename T>
      concept BidirectionIterator = ForwardIterator<T> && requires(T it)
      {
          {--it} -> std::convertible_to <T&>;
      };
      
    • 这种情况下的重载函数模板,如果某种类型同时满足重载函数模板中不同的 requires
      • 优先选择 requires 约束包含另一个候选的 requires 约束的模板进行实例化

E.3.2 约束和标签调度 (Constraints and Tag Dispatching)

  • 使用标签调度进行重载
    template<typename T>
    concept ForwardIterator = InputIterator<T> && requires
    {
        typename std::iterator_traits<T>::iterator_category;
        std::is_convertible_v<std::iterator_traits<T>::iterator_category, std::forward_iterator_tag>;
    };
    

E.4 Concept 技巧

E.4.1 Testing Concepts

  • 静态断言
    • static_assert(C<T1, T2, ...>, "Model failure");
  • 设计简单的测试用例(类型)
    • 算法或接口使用的类型是否需要支持移动/复制对象
    • 能接受哪种类型转换,哪一种是必须的
    • 模板假定的基本操作集是否唯一
      • 它可以使用 *=*= 操作?

E.4.2 Concept 粒度 (Concept Granularity)

  • 设计 Concept 库
  • Concept 的最佳设计原则
    • 对问题领域中出现的真实语义概念进行建模

E.4.3 二进制兼容性 (Binary Compatibility)

附录 D Standard Type Utilitie

D1 使用 Type Traits

  • 使用 type traits 需要引入 <type_traits> 头文件
  • trait 是生成类型或生成值的方式
    • 类型
      • typename std::trait<...>::type
      • std::trait_t<...> // C++14
      • std::trait<...>::value
      • std::trait<...>() //显式转换类型
      • std::trait_v<...> //C++17

D.1.1 std::integral_constant 和 std::bool_constant

  • 所有产生值的标准 Type Traits 都是从 std::integral_constant 的实例派生的
    template <class _Ty, _Ty _Val>
    struct integral_constant 
    {
        static constexpr _Ty value = _Val;
    
        using value_type = _Ty;
        using type       = integral_constant;
    
        constexpr operator value_type() const noexcept 
        {
            return value;
        }
        //C++14
        _NODISCARD constexpr value_type operator()() const noexcept 
        {
            return value;
        }
    };
    
    • 可以使用 value_type 成员来查询结果的类型
      • 由于许多 traits 产生的值都是谓词。因此,value_type 一般是 bool
    • trait 类型的对象具有到由 type trait 生成的值的类型的隐式类型转换
    • C++14,type trait 的对象也是函数对象(functor),其中 “函数调用” 产生它们的值
    • type 只生成底层的 integrate_constant 实例
  • std::bool_constant 可以基于 std::integrate_constant 定义
    template <bool _Val>
    using bool_constant = integral_constant<bool, _Val>;
    
    using true_type  = bool_constant<true>;
    using false_type = bool_constant<false>;
    

D.1.2 使用 trait 时应了解的事项

  • type trait 直接应用于类型,但 decltype 允许我们也测试表达式、变量和函数的属性
    • 只有当实体命名时没有多余的括号,decltype 才会生成变量或函数的类型
    • 对于任何其他表达式,它生成的类型也反映了表达式的类型类别
  • 对于新手程序员来说,某些 trait 可能具有非直觉行为
  • 有些 trait 有要求或前提条件,违反这些前提条件会导致行为不明确
  • 许多 trait 需要完整类型。为了能够将它们用于不完整类型,可以引入模板来推迟它们的评估
  • 有时,逻辑运算符 &&||! 不能用于定义基于其他 type trait 的新的 type trait
    • 此外,处理可能失败的 trait 可能会成为一个问题,或至少会导致一些缺陷出现
    • 为此,提供了特殊的 trait,允许逻辑组合布尔 trait
  • 尽管标准别名模板(以 _t_v 结尾)通常很方便,但它们也有缺点
    • 在某些元编程环境中无法使用

D.2 主要和复合类型类别 (Primary and Composite Type Categories)

D.2.1 主要类型类别

std::is_void<T>::value
  • 如果 T 是 void,则为 true
std::is_integral<T>::value
  • 如果 T 是以下类型,则为 true
    • bool
    • char 族类型
      • char、signed char、unsigned char、char、char16_t、char32_t,wchar_t
    • 整型族类型
      • signed 和 unsigned 的 short、int、long、long long、std::size_t、std::ptrdiff_t
      is_void_v<void>       // true
      is_void_v<void const> // true
      is_void_v<int>        // false
      
      void f();
      is_void_v<decltype(f)>   // false f为函数类型
      is_void_v<decltype(f())> // true
      
std::is_floating_point<T>::value
  • 如果 T 是 float、double、long double,则为 true
std::is_array<T>::value
  • 如果 T 是数组类型,则为 true
  • 根据语言规则声明为数组(有或无长度)的形参实际上是指针类型
  • std::array<> 不是数组类型,是类类型
    is_array_v<int[]>  // true
    is_array_v<int[5]> // true
    is_array_v<int*>   // false
    
    void foo(int a[], int b[5], int* c)
    {
        is_array_v<decltype(a)>  // false 指针类型
        is_array_v<decltype(b)>  // false 指针类型
        is_array_v<decltype(c)>  // false 指针类型
    }
    
std::is_pointer<T>::value
  • 如果 T 是指针,则为 true
  • 包括
    • 指向全局、静态、成员函数的指针
    • 数组或函数类型形参
  • 不包括
    • 成员指针类型
      • &X:m
      • X 是类类型,并且 m 是非静态成员函数或非静态数据成员
    • nullptr、std::nullptr_t
      is_pointer_v<int>               //false
      is_pointer_v<int*>              //true
      is_pointer_v<int* const>        //true
      is_pointer_v<int*&>             //false
      is_pointer_v<decltype(nullptr)> //false
      
      int* foo(int a[5], void(f)())
      {
          is_pointer_v<decltype(a)>        //true
          is_pointer_v<decltype(f)>        //true 为 void(*)() 类型
          is_pointer_v<decltype(foo)>      //false
          is_pointer_v<decltype(&foo)>     //true
          is_pointer_v<decltype(foo(a,f))> //true
      }
      
std::is_null_pointer<T>::value
  • 如果 T 是 std::nullptr_t, 即 nullptr 类型,则为 true
  • C++14
    is_null_pointer_v<decltype(nullptr)> //true
    
    void* p = nullptr;
    is_null_pointer_v<decltype(p)> //false p 不是 std::nullptr_t 类型
    
std::is_member_object_pointer<T>::value
std::is_member_function_pointer<T>::value
  • 如果 T 是成员指针类型,则为 true
    • int X::* 或 int (X::*)()
std::is_lvalue_reference<T>::value
std::is_rvalue_reference<T>::value
  • 如果 T 是左值或右值引用类型,则为 true
    is_lvalue_reference_v<int>    //false
    is_lvalue_reference_v<int&>   //true
    is_lvalue_reference_v<int&&>  //false
    is_lvalue_reference_v<void>   //false
    
    is_rvalue_reference_v<int>    //false
    is_rvalue_reference_v<int&>   //false
    is_rvalue_reference_v<int&&>  //true
    is_rvalue_reference_v<void>   //false
    
std::is_enum<T>::value
  • 如果 T 是枚举类型,则为 true
std::is_class<T>::value
  • 如果 T 是类类型,则为 true
  • 包括
    • 使用 class 和 struct 声明的类类型
    • 类模板
    • lambda 表达式
  • 不是类型的有
    • unions
    • 枚举类型
    • std::nullptr_t
      is_class_v<int>              //false
      is_class_v<std::string>      //true
      is_class_v<std::string const>//true
      is_class_v<std::string&>     //false
      
      auto l1 = []{};
      is_class_v<decltype(l1)> //true
      
std::is_union<T>::value
  • 如果 T 是 union 类型,则为 true
    • 包括 union 模板
std::is_function<T>::value
  • 如果 T 是函数类型,则为 true
  • 为 false 的情况有
    • 函数指针类型
    • lambda 表达式
    • 函数类型作为形参
      void foo(void(f)())
      {
      is_function_v<decltype(f)>      //false
      is_function_v<decltype(foo)>    //true
      is_function_v<decltype(&foo)>   //false
      is_function_v<decltype(foo(f))> //false
      }
      

D.2.2 复合类型类别

std::is_reference<T>::value
  • 如果 T 是引用类型,则为 true
  • 等价于
    • is_lvalue_reference_v<T> || is_rvalue_reference_v<T>
std::is_member_pointer<T>::value
  • 如果 T 是任何成员指针类型,则为 true
  • 等价于
    • is_member_object_pointer_v<T> || is_member_function_pointer_v<T>
std::is_arithmetic<T>::value
  • 如果 T 是算术类型,则为 true
    • bool
    • char族类型
    • 整形族类型
    • 浮点族类型
  • 等价于
    • is_integral_v<T> || is_floating_point_v<T>
std::is_fundamental<T>::value
  • 如果 T 是基本类型,则为 true
    • 算术类型
    • void
    • std::nullptr_t
  • 等价于
    • is_arithmetic_v<T> || is_void_v<T> || is_null_pointer_v<T>
  • 等价于
    • !is_compound_v<T>
std::is_scalar<T>::value
  • 如果 T 是标量类型,则为 true
  • 等价于
    • is_arithmetic_v<T> || is_enum_v<T> || is_pointer_v<T> || is_member_pointer_v<T> || is_null_pointer_v<T>
std::is_object<T>::value
  • 如果 T 是对象,则为 true
  • 等价于
    • is_scalar_v<T> || is_array_v<T> || is_class_v<T> || is_union_v<T>
  • 等价于
    • !(is_function_v<T> || is_reference_v<T> || is_void_v<T>)
std::is_compound<T>::value
  • 如果 T 是混合类型,则为 true
  • 等价于
    • !is_fundamental_v<T>
  • 等价于
    • is_enum_v<T> || is_array_v<T> || is_class_v<T> || is_union_v<T> || is_reference_v<T> || is_pointer_v<T> || is_member_pointer_v<T> || is_function_v<T>

D.3 类型属性和操作 (Type Properties and Operations)

D.3.1 其他类型属性

std::is_signed<T>::value
  • 如果 T 是有符号的算术类型,则为 true
    • 负值表示形式
    • int、float 等
  • bool 生成 false
  • char族类型根据具体类型产生 true 或 false
  • 所有非算术类型都会产生 false
std::is_unsigned<T>::value
  • 如果 T 是无符号的算术类型,则为 true
  • bool 生成 true
  • char族类型根据具体类型产生 true 或 false
  • 所有非算术类型都会产生 false
std::is_const<T>::value
  • 如果 T 带有 const 修饰的类型,则为 true
  • 注意常指针和常引用会产生 false
    is_const<int* const>::value //true
    is_const<int const*>::value //false
    is_const<int const&>::value //false
    
    is_const<int[3]>::value       //false
    is_const<int const[3]>::value //true
    is_const<int[]>::value        //false
    is_const<int const[]>::value  //true
    
std::is_volatile<T>::value
  • 如果 T 带有 volatile 修饰的类型,则为 true
  • 注意 volatile 修饰的指针和引用会产生 false
    is_volatile<int* volatile>::value //true
    is_volatile<int volatile*>::value //false
    is_volatile<int volatile&>::value //false
    
    is_volatile<int[3]>::value          //false
    is_volatile<int volatile[3]>::value //true
    is_volatile<int[]>::value           //false
    is_volatile<int volatile[]>::value  //true
    
std::is_aggregate<T>::value
  • C++17
  • 如果 T 是聚合类型,则为 true
    • 聚合类型
      • 数组、类、结构体、union
        • 没有用户定义的显式的或继承的构造函数
        • 没有私有或保护的非静态数据成员
        • 没有虚函数
        • 没有虚的、私有或包含的基类
  • 有助于确定是否需要列表初始化
    template<typename Coll, typename... T>
    void insert(Coll& coll, T&&... val)
    {
        if constexpr(!std::aggregate_v(typename Coll::value_type))
        {
            coll.emplace_back(std::forward<T>(val)...);
    
        }
        else
        {
            coll.emplace_back(typename Coll::value_type{std::forward<T>...});
        }
    }
    
  • 要求给定类型完整或是 void
std::is_trivial<T>::value
  • 如果 T 是 trivial 类型,则为 true
    • 标量类型
      • 整型、浮点型、枚举、指针
    • trivial 类类型
      • 没有虚函数
      • 没有虚基类
      • 没有用户定义的默认构造函数、移动/复制构造函数、赋值操作符、析构函数
      • 没有为非静态数据成员初始化
      • 没有 volatile 类型成员
      • 没有非普通(nontrival)成员
    • 数组
    • 上述类型的 cv-qualified 版本
  • 如果 is_trivially_copyable_v<T> 产生 true,并且存在 trivial 默认构造函数,则为true
  • 要求给定类型完整 或是 void
std::is_trivially_copyable<T>::value
  • 如果 T 是 trivially copyabl 类型,则为 true
    • 标量类型
    • trivial 类类型
    • 数组
    • 上述类型的 cv-qualified 版本
  • 产生的结果与 std::is_trivial<T> 相同,只是它可以为没有 trivial 默认构造函数的类类型产生true
  • is_standard_layout<> 相反
    • 不允许有 volatile 成员
    • 允许引用
    • 成员可以有不同的访问权限
    • 成员可以分布在不同的基类中
  • 要求给定类型完整 或是 void
std::is_standard_layout<T>::value
  • 如果 T 是 standard layout,则为 true
    • 标量类型
    • standard-layout 类类型
      • 没有虚函数
      • 没有虚基类
      • 没有非静态引用成员
      • 所有非静态成员在同一(基)类定义,并具有相同的访问权限
      • 所有成员必须是 standard-layout 类型
    • 数组
    • 上述类型的 cv-qualified 版本
  • std::is_trivial<> 相反
    • 允许有 volatile 成员
    • 不允许引用
    • 成员不允许有不同的访问权限
    • 成员不允许分布在不同的基类中
  • 要求给定类型完整 或是 void
std::is_pod<T>::value
  • 如果 T 是 plain old datatype (POD),则为 true
    • 这种类型的对象可以通过复制底层存储来进行复制
  • 等价于
    • !is_trivial_t<T> && is_standard_layout_v<T>
  • 产生 false 的情况
    • 没有 trivial 的默认构造函数、拷贝/移动 构造函数、拷贝/移动赋值、析构函数的类
    • 有虚成员或虚基类的类
    • 有 volatile 或引用成员的类
    • 成员在不同(基)类或不同访问权限的类
    • lambda 表达式类型
    • 函数类型
    • void
    • 由上述类型组成的类型
  • 要求给定类型完整或是 void
std::is_literal_type<T>::value
  • C++17 弃用
  • 如果 T 是 constexpr 函数的有效返回类型,则为 true
  • 如果 T 是字面量类型(literal type),则为 true
    • 标量
    • 引用类型
    • 至少有一个 constexpr 构造函数的类类型
      • 不是复制/移动构造函数((基)类中) ,
      • 在任何(基)类或成员中没有用户定义的或虚析构函数
      • 非静态数据成员的每次初始化都是常量表达式
    • 数组类型
  • 要求给定类型完整 或是 void
std::is_empty<T>::value
  • 如果 T 是类类型而不是 union 类型,并且对象不包含数据,则为 true
  • T 为类类型或结构体类型产生 ture 的情况
    • 没有非静态数据成员
    • 没有虚成员函数
    • 没有虚基类
    • 没有非空基类
  • 要求给定类型完整
    • 需是类类型或结构体
    • 不完整的 union
std::is_polymorphic<T>::value
  • 如果 T 是多态类类型,则为 true
    • 类声明或继承虚函数
  • 要求给定的类型要么是完整的,要么既不是类也不是结构
std::is_abstract<T>::value
  • 如果 T 是抽象类,则为 true
  • 如果给定类型是类/结构体,则要求它是完整的(不完整的 union 也可以)
std::is_final<T>::value
  • C++14
  • 如果 T 是 final 类,则为 true
  • 对于所有非类/union 类型(如int),返回false
  • 要求给定的类型 T 要么是完整的,要么不是类/结构或 union
std::has_virtual_destructor<T>::value
  • 如果 T 有一个虚拟析构函数,则为 true
  • 如果给定类型是类/结构体,则要求它是完整的(不完整的 union 也可以)
std::has_unique_object_representations<T>::value
  • C++17
  • 如果任何两个类型为 T 的对象在内存中具有相同的对象表示形式,则返回true
    • 两个相同的值总是使用相同的字节值序列来表示
  • 具有此属性的对象可以通过对相关的字节序列进行哈希来生成可靠的哈希值
    • 不存在某些不参与对象值的位可能因情况不同而不同的风险
  • 要求给定的类型是
    • 普通可复制的
    • 完整类型
    • void
    • 未知边界的数组
std::alignment_of<T>::value
  • 产生类型为 T 的对象的对齐值,类型为 std::size_t
    • 对于数组,为元素类型
    • 对于引用,为引用类型
  • 等价于
    • alignof(T)
  • 该 trait 于 alignof(...)之前在 C++11 中引入
    • 该 trait 可以作为类类型传递,这对某些元程序很有用
  • 要求 alignof(T) 是一个有效的表达式
  • 使用 aligned_union<>获得多个类型的通用对齐
std::rank<T>::value
  • std::size_t 形式生成 T 类型数组的维数
  • 其他类型生成 0
  • 指针没有任何关联的维度
  • 数组类型中未指定边界,也会有指定的维度
  • 用数组类型作为函数的形参也不是数组类型
  • std:array 不是数组类型
    int a2[5][7];
    rank_v<decltype(a2)>; //2
    rank_v<int *>;        //0
    
    extern int p1[];
    rank_v<decltype(p1)>; //1
    
std::extent<T>::value
std::extent<T, IDX>::value
  • 产生 T 类型数组的第一维或第 IDX 维的大小,类型为 std::size_t
  • 如果 T 不是数组、维度不存在或维度的大小未知,则返回 0
    int a2[5][7];
    extent_v<decltype(a2)>;    //5
    extent_v<decltype(a2), 0>; //5
    extent_v<decltype(a2), 1>; //7
    extent_v<decltype(a2), 2>; //0
    
    extent_v<int*>; //0
    extern int p1[];
    extent_v<decltype(p1)>; //0
    
std::underlying_type<T>::value
  • 生成枚举类型 T 的基础类型
  • 要求给定类型是完整的枚举类型。对于其他类型,则未定义
std::is_invocalble<T, Args...>::value
std::is_nothrow_invocalble<T, Args...>::value
  • C++17
  • 如果 T 可用使用 Args... 调用函数,则返回true
    • 需保证不抛异常
  • 用于测试 std:invoke 或函数是否可以使用 Args... 调用
  • 要求给定类型完整
    • 或是 void
    • 或是未知边界的数组
      struct C
      {
          bool operator()(int) const
          {
              return true;
          }
      };
      
      std::is_invocalble<C>::value;       //false
      std::is_invocalble<C, int>::value;  //true
      std::is_invocalble<int*>::value;    //false
      std::is_invocalble<int(*)()>::value;//true
      
std::is_invocalble_r<RET_T, T, Args...>::value
std::is_nothrow_invocalble_r<RET_T, T, Args...>::value
  • C++17
  • 如果 T 可用使用 Args... 调用函数, 返回一个可转换为 RET_T 类型的值,则返回true
    • 需保证不抛异常
  • 用于测试 std:invoke 或函数是否可以使用 Args... 调用,并返回 RET_T 类型的值
  • 要求给定类型完整
    • 或是 void
    • 或是未知边界的数组
      struct C
      {
          bool operator()(int) const
          {
              return true;
          }
      };
      
      std::is_invocalble<bool,C, int>::value;              //true
      std::is_invocalble<int, C, long>::value;             //true
      std::is_invocalble<void, C ,int>::value;             //true ?
      std::is_invocalble<char*, C, int>::value;            //false
      std::is_invocalble<long, int(*)(int)>::value;        //false
      std::is_invocalble<long, int(*)(int), int>::value;   //true
      std::is_invocalble<long, int(*)(int), double>::value;//true
      
std::invoke_result<T, Args...>::value
std::result_of<T, Args...>::value
  • 生成使用 Args... 作为参数的可调用 T 类型的返回类型
  • 注意这类个 trait 的实参形式不同 (看示例)
  • 要求给定类型完整
    • 或是 void
    • 或是未知边界的数组
  • invoke_result 允许使用抽象类型
  • result_of 在 C++17 被弃用
    std::string foo(int);
    
    using R0 = typename std::result_of<decltype(&foo)(int)>::type; //C++11
    using R1 = std::result_of_t<decltype(&foo)(int)>;              //C++14
    using R2 = std::invoke_result_t<decltype(foo), int>;           //C++17
    
    struct ABC
    {
        virtual ~ABC() = 0;
        void operator()(int) const
        {
    
        }
    };
    
    using T1 = typename std::result_of<ABC(int)>::type;    //Error
    using T2 = typename std::invoke_result<ABC, int>::type //Ok c++17
    

D.3.2 特定操作

std::is_constructible<T, Args...>::value
std::is_trivially_constructible<T, Args...>::value
std::is_nothrow_constructible<T, Args...>::value
  • 如果 T 类型的对象可以用 Args... 作为参数初始化, 则返回 true
    • T t(std::declval<Args>() ...);
  • 如果为 ture,该对象也能被析构
    • is_destructible_v<T>, is_trivially_destructible_v<T>, is_nothrow_destructible_v<T>
      • 都为 true
  • 要求给定类型完整
    • 或是 void
    • 或是未知边界的数组
    is_constructible_v<int>        //true
    is_constructible_v<int, int>   //true
    is_constructible_v<long, int>  //true
    is_constructible_v<int, void*> //false
    is_constructible_v<void*, int> //false
    is_constructible_v<char const*, std::string>           //false
    is_constructible_v<std::string, char const*>           //true
    is_constructible_v<std::string, char const*, int, int> //true
    
std::is_default_constructible<T>::value
std::is_trivially_default_constructible<T>::value
std::is_nothrow_default_constructible<T>::value
  • 如果 T 类型的对象可以在没有任何初始化参数的情况下初始化,则返回 true

  • 分别等价于

    • is_constructible_v<T>
    • is_trivially_constructible_v<T>
    • is_nothrow_constructible_v<T>
  • 如果为 ture,该对象也能被析构

    • is_destructible_v<T>, is_trivially_destructible_v<T>, is_nothrow_destructible_v<T>
  • 要求给定类型完整

    • 或是 void
    • 或是未知边界的数组
std::is_copy_constructible<T>::value
std::is_trivially_copy_constructible<T>::value
std::is_nothrow_copy_constructible<T>::value
  • 如果可以通过复制 T 类型的另一个值来创建 T 类型的对象,则返回 true
  • 如果 T 不是可引用类型,则返回 false
    • void
    • constvolatile&和或&& 修饰的函数类型
  • 如果 T 是可引用的类型,分别与下列返回类型相同
    • is_constructible<T, T const&>::value
    • is_trivially_constructible<T, T const&>::value
    • is_nothrow_constructible<T, T const&>::value
  • 为了确定 T 的对象是否可以从 T 类型的右值进行拷贝构造,可以使用
    • is_constructible<T, T&&>
    • is_trivially_constructible<T, T&&>
    • is_nothrow_constructible<T, T&&>
  • 如果为 ture,该对象也能被析构
    • is_destructible_v<T>, is_trivially_destructible_v<T>, is_nothrow_destructible_v<T>
  • 要求给定类型完整
    • 或是 void
    • 或是未知边界的数组
    is_copy_constructible_v<int>                      //true
    is_copy_constructible_v<void>                     //false
    is_copy_constructible_v<std::unique_ptr<int>>     //false
    is_copy_constructible_v<std::string>              //true
    is_copy_constructible_v<std::string&>             //true
    is_copy_constructible_v<std::string&&>            //false*
    //比较项
    is_constructible_v<std::string, std::string>      //true *
    is_constructible_v<std::string&, std::string&>    //true
    is_constructible_v<std::string&&, std::string&&>  //true
    
std::is_move_constructible<T>::value
std::is_trivially_move_constructible<T>::value
std::is_nothrow_move_constructible<T>::value
  • 如果可以从 T 类型的右值创建 T 类型的对象,则返回true
  • 如果 T 不是可引用类型,则返回 false
    • void
    • constvolatile&和或&& 修饰的函数类型
  • 如果 T 是可引用的类型,分别与下列返回类型相同
    • is_constructible<T, T const&&>::value
    • is_trivially_constructible<T, T const&&>::value
    • is_nothrow_constructible<T, T const&&>::value
  • 为了确定 T 的对象是否可以从 T 类型的右值进行拷贝构造,可以使用
    • is_constructible<T, T&&>
    • is_trivially_constructible<T, T&&>
    • is_nothrow_constructible<T, T&&>
  • 如果为 ture,该对象也能被析构
    • is_destructible_v<T>, is_trivially_destructible_v<T>, is_nothrow_destructible_v<T>
  • 注意
    • 如果无法直接为 T 类型的对象调用移动构造函数,则无法检查它是否抛出
    • 构造函数应是 public 的且没有 delete
    • 还要求相应的类型不是抽象类
    is_move_constructible_v<int>                      //true
    is_move_constructible_v<void>                     //false
    is_move_constructible_v<std::unique_ptr<int>>     //true
    is_move_constructible_v<std::string>              //true
    is_move_constructible_v<std::string&>             //true
    is_move_constructible_v<std::string&&>            //true
    //比较项
    is_constructible_v<std::string, std::string>      //true
    is_constructible_v<std::string&, std::string&>    //true
    is_constructible_v<std::string&&, std::string&&>  //true
    
std::is_assignable<TO, FROM>::value
std::is_trivially_assignable<TO, FROM>::value
std::is_nothrow_assignable<TO, FROM>::value
  • 如果可以将 FROM 类型的对象分配给 TO 类型的对象,则返回 true
  • 要求给定类型完整
    • 或是 void
    • 或是未知边界的数组
  • 使用非引用类型,非类类型作为第一个参数,总是返回 false
    • 因为这些类型都会产生纯右值,将值赋值到纯右值是非法的
  • 注意,is_convertible 对源类型和目标类型有不同的顺序
    is_assignable_v<int, int>                     //false
    is_assignable_v<int&, int>                    //true
    is_assignable_v<int&&, int>                   //false
    is_assignable_v<int&, int&>                   //true
    is_assignable_v<int&&, int&&>                 //false
    is_assignable_v<int&, long&>                  //true
    is_assignable_v<int&, void*>                  //false 
    is_assignable_v<void*, int>                   //false
    is_assignable_v<void*, int&>                  //false
    is_assignable_v<std::string, std::string>     //true
    is_assignable_v<std::string&, std::string&>   //true
    is_assignable_v<std::string&&, std::string&&> //true
    
std::is_copy_assignable<T>::value
std::is_trivially_copy_assignable<T>::value
std::is_nothrow_copy_assignable<T>::value
  • 如果 T 类型的值可以(copy)赋值给 T 类型的对象,则返回true
  • 如果 T 不是可引用类型,则返回 false
    • void
    • constvolatile&和或&& 修饰的函数类型
  • 如果 T 是可引用的类型,分别与下列返回类型相同
    • is_assignable<T&, T const&>::value
    • is_trivially_assignable<T&, T const&>::value
    • is_nothrow_assignable<T&, T const&>::value
  • 要确定 T 类型的右值是否可以复制赋值给 T 类型的另一个右值
    • is_assignable<T&&, T&&>
    • is_trivially_assignable<T&&, T&&>
    • is_nothrow_assignable<T&&, T&&>
  • void、内置数组类型和带有删除拷贝赋值运算符的类不能被拷贝赋值
  • 要求给定类型完整
    • 或是 void
    • 或是未知边界的数组
    is_copy_assignable_v<int>                   //true
    is_copy_assignable_v<int&>                  //true
    is_copy_assignable_v<int&&>                 //true
    is_copy_assignable_v<void>                  //false
    is_copy_assignable_v<void*>                 //true
    is_copy_assignable_v<char[]>                //false
    is_copy_assignable_v<std::string>           //true 
    is_copy_assignable_v<std::unique_ptr<int>>  //false
    
std::is_move_constructible<T>::value
std::is_trivially_move_constructible<T>::value
std::is_nothrow_move_constructible<T>::value
  • 如果 T 类型的右值可以移动赋值给 T 类型的对象,则返回true
  • 如果 T 不是可引用类型,则返回 false
    • void
    • constvolatile&和或&& 修饰的函数类型
  • 如果 T 是可引用的类型,分别与下列返回类型相同
    • is_assignable<T&, T&&>::value
    • is_trivially_assignable<T&, T&&>::value
    • is_nothrow_assignable<T&, T&&>::value
  • void、内置数组类型和带有删除移动赋值运算符的类不能被移动赋值
  • 要求给定类型完整
    • 或是 void
    • 或是未知边界的数组
    is_move_assignable_v<int>                   //true
    is_move_assignable_v<int&>                  //true
    is_move_assignable_v<int&&>                 //true
    is_move_assignable_v<void>                  //false
    is_move_assignable_v<void*>                 //true
    is_move_assignable_v<char[]>                //false
    is_move_assignable_v<std::string>           //true 
    is_move_assignable_v<std::unique_ptr<int>>  //true
    
std::is_destructible<T>::value
std::is_trivially_destructible<T>::value
std::is_nothrow_destructible<T>::value
  • 如果可以销毁 T 类型的对象,则返回true
  • 引用类型返回 true
  • void、未知边界数组类型和函数类型,返回 false
  • is_trivially_destructible 返回 true
    • T 的析构函数、任何基类或任何非静态数据成员都不是用户定义的或虚的
  • 要求给定类型完整
    • 或是 void
    • 或是未知边界的数组
    is_destructible_v<void>                        //false
    is_destructible_v<int>                         //true
    is_destructible_v<std::string>                 //true
    is_destructible_v<std::pair<int, std::string>> //true
    
    is_trivially_destructible_v<void>                        //false
    is_trivially_destructible_v<int>                         //true
    is_trivially_destructible_v<std::string>                 //false
    is_trivially_destructible_v<std::pair<int, int>>         //true
    is_trivially_destructible_v<std::pair<int, std::string>> //false
    
std::is_swappable_with<T1, T2>::value
std::is_nothrow_swappable_with<T1, T2>::value
  • C++17
  • 如果 T1 类型的表达式可以与 T2 类型的表达式交换,则返回true,
    • 需保证不会引发异常
  • 要求给定类型完整
    • 或是 void
    • 或是未知边界的数组
  • 注意,对于非引用类型,非类类型作为第一个或第二个类型,总会返回 flase
    • 因为这类类型会产生 prvalue,swap是无效的
    is_swappable_with_v<int, int>      //false
    is_swappable_with_v<int&, int>     //false
    is_swappable_with_v<int&&, int>    //false
    is_swappable_with_v<int&, int&>    //true
    is_swappable_with_v<int&&, int&&>  //false
    is_swappable_with_v<int&, long&>   //false
    is_swappable_with_v<int&, void*>   //false
    is_swappable_with_v<void*, int>    //false
    is_swappable_with_v<void*, int&>   //false
    is_swappable_with_v<std::string, std::string>     //false
    is_swappable_with_v<std::string&, std::string&>   //true
    is_swappable_with_v<std::string&&, std::string&&> //false
    
std::is_swappable<T>::value
std::is_nothrow_swappable<T>::value
  • C++17
  • 如果 T 类型的左值能被交换,则返回 true
  • 如果 T 是可引用的类型,分别与下列返回类型相同
    • is_swappable_with<T&, T const&>::value
    • is_nothrow_swappable_with<T&, T&>::value
  • 如果 T 不是可引用类型,则返回 false
    • void
    • constvolatile&和或&& 修饰的函数类型
  • 要确定 T 的右值是否可以与 T 的另一右值交换,使用
    • is_swappable_with<T&&, T&&>
  • 要求给定类型完整
    • 或是 void
    • 或是未知边界的数组
    is_swappable_v<int>                  //true
    is_swappable_v<int&>                 //true
    is_swappable_v<int&&>                //true
    is_swappable_v<std::string&&>        //true
    is_swappable_v<void>                 //false
    is_swappable_v<void*>                //true
    is_swappable_v<char[]>               //false
    is_swappable_v<std::unique_ptr<int>> //true
    

D.3.3 类型之间的关系

std::is_same<T1, T2>::value
  • 如果 T1 和 T2 为同一类型,则返回 true
    • 可以是使用 const he volatile 修饰的类型
  • 如果一个类型是另一个该类型的别名,返回 true
  • 如果两个对象由相同类型的对象初始化,则返回true
  • 与两个不同的 lambda 表达式关联的(闭包)类型的结果为 false,
    • 即使它们定义了相同的行为也为 false
    auto a = nullptr;
    auto b = nullptr;
    is_same_v<decltype(a), decltype(b)> //true
    
    using A = int;
    is_same_v<A, int> //true
    
    auto x = [](int){};
    auto y = x;
    auto z = [](int){};
    is_same_v<decltype(x), decltype(y)> //true
    is_same_v<decltype(x), decltype(z)> //false
    
std::is_base_of<B, D>::value
  • 如果 B 是 D 的基类或 B 与 D 是同一类,则为 true
    • 无论类型是 cv-qualified 的、使用私有继承还是保护继承
    • D 有多个 B 类型的基类
    • D 通过多个继承路径(通过虚继承)将 B 作为基类
  • 如果至少有一个类型是 union,则返回false
  • 要求类型 D 是完整的
    • 与 B 相同的类型
    • 既不是结构体也不是类
    class B {};
    class D1 : B {};
    class D2 : B {};
    class DD : private D1, private D2 {};
    is_base_of_v<B, D1>       //true
    is_base_of_v<B, DD>       //true
    is_base_of_v<B, const DD> //true
    is_base_of_v<B, DD const> //true
    is_base_of_v<B, B const>  //true
    is_base_of_v<B&, DD&>     //false
    is_base_of_v<B[3], DD[3]> //false
    is_base_of_v<int, int>    //false
    
std::is_convertible<FROM, TO>::value
  • 如果 FROM 类型的表达式可转换为 TO 类型,则返回 true
    • FROM 类型顶部的引用仅用于确定要转换的表达式的值类别
    • 基础类型就是源表达式的类型。
  • 注意,is_constructible并不总是意味着 is_convertible
    class C
    {
    public:
      explicit C(C const&);
      ...
    }
    
    is_constructible_v<C, C> //true
    is_convertible_v<C, C>  //false
    
  • 要求给定类型完整
    • 或是 void
    • 或是未知边界的数组
  • 注意,is_constructibleis_assignable 的源类型和目标类型的顺序不同

D.4 类型构造 (Type Construction)

std::remove_const<T>::type

std::remove_volatile<T>::type

std::remove_cv<T>::type

  • 生成类型 T
    • 顶层不带 const 或/和 volatile
  • 注意,常指针是 const-qualified 类型,而非常指针或对常量类型的引用不是const-qualified 类型
    remove_cv_t<int>                //int
    remove_const_t<int const>       //int
    remove_cv_t<int const volatile> //int
    remove_const_t<int const&>      //int const& 
    
  • 类型构造 traits 的应用顺序很重要
    remove_const_t<remove_reference_t<int const&>> //int
    remove_reference_t<remove_const_t<int const&>> //int cosnt
    
  • std:decay<>更便于使用
    • 然而,它也会将数组和函数类型转换为相应的指针类型
    • decay_t<int const&> //int

std::add_const<T>::type

std::add_volatile<T>::type

std::add_cv<T>::type

  • 在 T 类型顶层添加 const 或/和 volatile 限定符
  • 用于引用类型或函数类型没有效果
    add_cv_t<int>               //int const volatile
    add_cv_t<int const>         //int const volatile
    add_cv_t<int const volatile>//int const volatile
    add_const_t<int>            //int const
    add_const_t<int const>      //int const
    add_const_t<int&>           //int&
    

std::make_signed<T>::type

std::make_unsigned<T>::type

  • 生成相应的有符号/无符号类型 T
  • 要求 T 是枚举类型或型
    • 不是 bool
    • 所有其他类型都会导致未定义行为
  • 用于引用类型或函数类型没有任何效果
    make_unsigned_t<char>       //unsigned char
    make_unsigned_t<int>        //unsigned int
    make_unsigned_t<int const&> //未定义行为
    

std::remove_reference<T>::type

  • 生成引用类型 T 引用的类型(如果不是引用类型,则返回 T 本身)
    remove_reference_t<int>        //int
    remove_reference_t<int const>  //int const
    remove_reference_t<int const&> //int const
    remove_reference_t<int&&>      //int
    
  • 注意,引用类型本身不是常量类型。因此,应用类型构造 trait 的顺序很重要
    remove_const_t<remove_reference_t<int const&>> //int
    remove_reference_t<remove_const_t<int const&>> //int cosnt
    
  • std:decay<>更便于使用
    • 然而,它也会将数组和函数类型转换为相应的指针类型
    • decay_t<int const&> //int

std::add_lvalue_reference<T>::type

std::add_rvalue_reference<T>::type

  • 如果 T 是可引用类型,则生成对 T 的左值或右值引用
  • 如果 T 不是可引用类型,则返回 false
    • void
    • constvolatile&和或&& 修饰的函数类型
  • 注意,如果 T 已经是引用类型,则使用引用折叠规则
    add_lvalue_reference_t<int> //int&
    add_rvalue_reference_t<int> //int&&
    add_rvalue_reference_t<int const> //int const&&
    add_lvalue_reference_t<int const&> //int const&
    add_rvalue_reference_t<int const&> //int const&
    add_rvalue_reference_t<remove_referencce_t<int const>> //int&&
    add_rvalue_reference_t<void> //void
    add_lvalue_reference_t<void> //void
    

std::remove_pointer<T>::type

  • 生成指针类型 T 指向的类型(如果不是指针类型,则返回 T 本身)
    remove_pointer_t<int> //int
    remove_pointer_t<int const*> //int const
    remove_pointer_t<int const* const* const> //int const* const
    

std::add_pointer<T>::type

  • 返回指向 T 的指针的类型。 或如果是引用类型 T,则返回指向底层类型 T 的指针类型
  • 如果没有此类型返回 T
    add_pointer_t<void> //void*
    add_pointer_t<int const* const> //int const* const*
    add_pointer_t<int&>             //int*
    add_pointer_t<int[3]>           //int(*)[3]
    add_pointer_t<void(&)(int)>     //void(*)int
    add_pointer_t<void(int)>        //void(*)int
    add_pointer_t<void(int) const>  //void(int) const
    

std::remove_extent<T>::type

std::remove_all_extent<T>::type

  • 给定一个数组类型,remove_extent 生成它的直接元素类型
    • 生成的元素可以是数组类型
  • remove_all_extent 剥离所有 “数组层” 以生成底层元素类型
    • 因此不再是数组类型。如果 T 不是数组类型,则生成 T 自己的类型
  • 指针没有任何关联的维度
    remove_extent_t<int>        //int
    remove_extent_t<int[10]>    //int
    remove_extent_t<int[5][10]> //int[10]
    remove_extent_t<int[][10]>  //int[10]
    remove_extent_t<int*>       //int*
    
    remove_all_extent_t<int>        //int
    remove_all_extent_t<int[10]>    //int
    remove_all_extent_t<int[5][10]> //int
    remove_all_extent_t<int[][10]>  //int
    remove_all_extent_t<int(*)[5]>  //int(*)[5]
    

std::decay<T>::type

  • 产生退化类型的 T
  • 具体而言,对于类型 T,执行以下转换
    • 首先应用 remove_reference
    • 如果结果是数组类型,则生成指向元素类型的指针
    • 否则,如果结果是一个函数类型,则生成该函数类型使用 add_ pointer 生成的类型
    • 否则,生成的结果没有任何顶层 const/volatile 限定符
  • 当初始化 auto 类型的对象时,decay<> 通过参数的值传递或类型转换来建模
  • decay<> 对于处理可能被引用类型替换但用于确定另一个函数的返回类型或参数类型的模板参数特别有用
    decay_t<int const&>    //int 
    decay_t<int const[4]>  //int const*
    void foo();
    decay_t<decltype(foo)> //void(*)()
    

D.5 其他 traits

std::enable_if<cond>::type

std::enable_if<cond, T>::type

  • 如果 cond 为 true,则在其成员 type 中生成 void 或 T。否则,不会定义成员 type
  • 由于当 cond 为 false 时未定义类型成员,因此该 trait 可以并且通常用于根据给定条件禁用或 SFINAE 函数模板

std::conditional<cond, T, F>::type

  • 如果 cond 为 true,则返回 T,否则返回 F
  • 注意,与正常的 C++ if-then-else 语句不同,“then” 和 “else” 分支的模板参数都是在选择之前计算的,因此两个分支都可能包含格式错误的代码,或者可能的程序格式错误。因此,可能需要添加一个间接层,以避免当分支不被使用时,对 “then” 和 “else” 分支中的表达式求值

std::common_type<T...>::type

  • 生成 T... 中公共类型
  • 最终生成的类型是使用 decay_t 退化过的类型
  • 如果无法生成公共类型,则会报错
  • 如果只给一个类型,则生成该类型的退化类型 decay_t

std::aligned_union<MIN_SZ, T...>::type

  • 生成可作为未初始化存储的普通旧数据类型(POD),其大小至少为 MIN_SZ,适合保存任何给定类型 T1, T2, ...,Tn
  • 它生成一个静态成员 alignment_value ,该值是所有给定类型中最严格的对齐,对于结果类型,该值等于
    • std::alignment_of_v<type>::value
    • alignof(type)
  • 要求至少给定一个类型
  • 示例
    using POD_T = std::aligned_union_t<0, char, std::pair<std::string, std::string>>;
    std::cout << sizeof(POD_T) << '\n'
    std::cout << std::aligned_union<0, char, std::pair<std::string, std::string>>::alignment_value << '\n';
    

std::aligned_storage<MAX_TYPE_SZ>::type

std::aligned_storage<MAX_TYPE_SZ, DEF_ALIGN>::type

  • 生成一个可用作未初始化存储的普通旧数据类型(POD),其大小可容纳所有可能的类型,最大为 MAX_TYPE_SZ,同时考虑默认对齐或作为 DEF_ALIGN 传递的对齐
  • 要求 MAX_TYPE_SZ 大于零,且至少有一种类型的对齐值方式为 DEF_ALIGN
  • 示例
    using POD_T = std::aligned_storage_t<5>;
    

D.6 组合 Type Traits (Combining Type Traits)

std::conjunction<B...>::value

std::disjunction<B...>::value

  • C++17

  • 是否所有或一个传递的布尔 trait B... 是 true

  • 使用 &&|| 作用于传递的 trait

    static_assert(std::disjunction<std::is_constructible<X, int>, std::is_constructible<Y, int>>{}, "cant init X or Y from int");
    

std::negation<B>::value

  • C++17
  • 是否传递的布尔 trait B 是 false
  • 使用 ! 作用于传递的 trait
    template<typename T>
    struct isNoPtrT : std::negation<std::disjunction<std::is_null_pointer<T>, std::is_member_pointer<T>, std::is_pointer<T>>>
    {
    
    };
    

D.7 Other Utilities

std::declval<T>()

  • 在头文件 <utility> 中定义
  • 在不调用任何构造函数或初始化的情况下生成任何类型的 “对象” 或函数
  • 如果是 void,返回类型为 void
  • 这可以用于处理未经估值的(unevaluated)表达式中任何类型的对象或函数
  • 定义如下
    template<typename T>
    add_rvalue_reference_t<T> declval() noexcept;
    
    • 如果 T 是普通类型或右值引用,则生成T&&
    • 如果 T 是左值引用,则生成T&
    • 如果 T 是 void 类型,则生成 void

std::addressof(r)

  • 在头文件 <memory> 中定义
  • 生成对象或函数 r 的地址,即使 operator& 被重载
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值