89、C++ 类型特性模板详解

C++ 类型特性模板详解

在 C++ 编程中,类型特性模板是一组非常有用的工具,它们允许我们在编译时查询类型的属性和关系。本文将详细介绍这些类型特性模板,包括辅助类、一元类型特性、类型属性查询等内容。

1. 辅助类

在 C++ 标准库中,有一个重要的辅助类模板 integral_constant ,其定义如下:

namespace std {
    template<class T, T v> struct integral_constant {
        static constexpr T value = v;
        using value_type = T;
        using type = integral_constant<T, v>;
        constexpr operator value_type() const noexcept { return value; }
        constexpr value_type operator()() const noexcept { return value; }
    };
}

integral_constant 类模板用于定义一个编译时常量,它包含一个静态常量成员 value ,其值为模板参数 v 。该类还定义了类型别名 value_type type ,以及两个常量成员函数,用于返回 value 的值。这个类模板常被用作各种类型特性的基类,帮助定义不同类型特性的接口。

2. 一元类型特性

一元类型特性模板可以在编译时查询类型的属性,下面将从主要类型类别、复合类型特性和类型属性三个方面进行介绍。

2.1 主要类型类别

主要类型类别模板对应于 C++ 标准中 6.7 节的描述,用于判断一个类型属于哪种基本类型类别。以下是主要类型类别模板及其条件和注释:
| 模板 | 条件 | 注释 |
| — | — | — |
| template<class T> struct is_void; | T void 类型 | - |
| template<class T> struct is_null_pointer; | T nullptr_t 类型 | - |
| template<class T> struct is_integral; | T 是整数类型 | - |
| template<class T> struct is_floating_point; | T 是浮点类型 | - |
| template<class T> struct is_array; | T 是数组类型(已知或未知范围) | 类模板 array 不是数组类型 |
| template<class T> struct is_pointer; | T 是指针类型 | 包括函数指针,但不包括非静态成员指针 |
| template<class T> struct is_lvalue_reference; | T 是左值引用类型 | - |
| template<class T> struct is_rvalue_reference; | T 是右值引用类型 | - |
| template<class T> struct is_member_object_pointer; | T 是数据成员指针 | - |
| template<class T> struct is_member_function_pointer; | T 是成员函数指针 | - |
| template<class T> struct is_enum; | T 是枚举类型 | - |
| template<class T> struct is_union; | T 是联合类型 | - |
| template<class T> struct is_class; | T 是非联合类类型 | - |
| template<class T> struct is_function; | T 是函数类型 | - |

对于任何给定的类型 T ,应用这些模板到 T cv T 应得到相同的结果,并且对于任何类型 T ,只有一个主要类型类别模板的 value 成员会计算为 true

2.2 复合类型特性

复合类型特性模板提供了主要类型类别的方便组合,对应于 C++ 标准中 6.7 节的描述。以下是复合类型特性模板及其条件和注释:
| 模板 | 条件 | 注释 |
| — | — | — |
| template<class T> struct is_reference; | T 是左值引用或右值引用 | - |
| template<class T> struct is_arithmetic; | T 是算术类型 | - |
| template<class T> struct is_fundamental; | T 是基本类型 | - |
| template<class T> struct is_object; | T 是对象类型 | - |
| template<class T> struct is_scalar; | T 是标量类型 | - |
| template<class T> struct is_compound; | T 是复合类型 | - |
| template<class T> struct is_member_pointer; | T 是成员指针类型 | - |

同样,对于任何给定的类型 T ,应用这些模板到 T cv T 应得到相同的结果。

2.3 类型属性

类型属性模板提供了对类型一些重要属性的访问,以下是部分类型属性模板及其条件和前置条件:
| 模板 | 条件 | 前置条件 |
| — | — | — |
| template<class T> struct is_const; | T const 限定的 | - |
| template<class T> struct is_volatile; | T volatile 限定的 | - |
| template<class T> struct is_trivial; | T 是平凡类型 | remove_all_extents_t<T> 应为完整类型或 cv void |
| template<class T> struct is_trivially_copyable; | T 是可平凡复制的类型 | remove_all_extents_t<T> 应为完整类型或 cv void |
| template<class T> struct is_standard_layout; | T 是标准布局类型 | remove_all_extents_t<T> 应为完整类型或 cv void |
| template<class T> struct is_empty; | T 是类类型(非联合),无非静态数据成员(除零大小子对象外),无虚成员函数,无虚基类,且所有基类 B 满足 is_empty_v<B> true | 若 T 是非联合类类型, T 应为完整类型 |
| template<class T> struct is_polymorphic; | T 是多态类 | 若 T 是非联合类类型, T 应为完整类型 |
| template<class T> struct is_abstract; | T 是抽象类 | 若 T 是非联合类类型, T 应为完整类型 |
| template<class T> struct is_final; | T 是用 final 标记的类类型 | 若 T 是类类型, T 应为完整类型 |
| template<class T> struct is_aggregate; | T 是聚合类型 | remove_all_extents_t<T> 应为完整类型或 cv void |
| template<class T> struct is_signed; | 若 is_arithmetic_v<T> true ,结果与 T(-1) < T(0) 相同;否则为 false | - |
| template<class T> struct is_unsigned; | 若 is_arithmetic_v<T> true ,结果与 T(0) < T(-1) 相同;否则为 false | - |
| template<class T, class... Args> struct is_constructible; | 对于函数类型 T cv void 类型 T is_constructible_v<T, Args...> false ,否则见下文 | T 和所有 Args 类型应为完整类型、 cv void 或未知边界的数组 |
| template<class T> struct is_default_constructible; | is_constructible_v<T> true | T 应为完整类型、 cv void 或未知边界的数组 |
| template<class T> struct is_copy_constructible; | 对于可引用类型 T ,结果与 is_constructible_v<T, const T&> 相同,否则为 false | T 应为完整类型、 cv void 或未知边界的数组 |
| template<class T> struct is_move_constructible; | 对于可引用类型 T ,结果与 is_constructible_v<T, T&&> 相同,否则为 false | T 应为完整类型、 cv void 或未知边界的数组 |
| template<class T, class U> struct is_assignable; | 表达式 declval<T>() = declval<U>() 作为未计算操作数时格式良好 | T U 应为完整类型、 cv void 或未知边界的数组 |
| template<class T> struct is_copy_assignable; | 对于可引用类型 T ,结果与 is_assignable_v<T&, const T&> 相同,否则为 false | T 应为完整类型、 cv void 或未知边界的数组 |
| template<class T> struct is_move_assignable; | 对于可引用类型 T ,结果与 is_assignable_v<T&, T&&> 相同,否则为 false | T 应为完整类型、 cv void 或未知边界的数组 |
| template<class T, class U> struct is_swappable_with; | 表达式 swap(declval<T>(), declval<U>()) swap(declval<U>(), declval<T>()) 在可交换值的重载解析上下文中作为未计算操作数时格式良好 | T U 应为完整类型、 cv void 或未知边界的数组 |
| template<class T> struct is_swappable; | 对于可引用类型 T ,结果与 is_swappable_with_v<T&, T&> 相同,否则为 false | T 应为完整类型、 cv void 或未知边界的数组 |
| template<class T> struct is_destructible; | T 是引用类型,或者 T 是完整对象类型,且表达式 declval<U&>().~U() 作为未计算操作数时格式良好( U remove_all_extents_t<T> ) | T 应为完整类型、 cv void 或未知边界的数组 |
| template<class T, class... Args> struct is_trivially_constructible; | is_constructible_v<T, Args...> true ,且 is_constructible 的变量定义已知不调用非平凡操作 | T 和所有 Args 类型应为完整类型、 cv void 或未知边界的数组 |
| template<class T> struct is_trivially_default_constructible; | is_trivially_constructible_v<T> true | T 应为完整类型、 cv void 或未知边界的数组 |
| template<class T> struct is_trivially_copy_constructible; | 对于可引用类型 T ,结果与 is_trivially_constructible_v<T, const T&> 相同,否则为 false | T 应为完整类型、 cv void 或未知边界的数组 |
| template<class T> struct is_trivially_move_constructible; | 对于可引用类型 T ,结果与 is_trivially_constructible_v<T, T&&> 相同,否则为 false | T 应为完整类型、 cv void 或未知边界的数组 |
| template<class T, class U> struct is_trivially_assignable; | is_assignable_v<T, U> true ,且赋值操作已知不调用非平凡操作 | T U 应为完整类型、 cv void 或未知边界的数组 |
| template<class T> struct is_trivially_copy_assignable; | 对于可引用类型 T ,结果与 is_trivially_assignable_v<T&, const T&> 相同,否则为 false | T 应为完整类型、 cv void 或未知边界的数组 |
| template<class T> struct is_trivially_move_assignable; | 对于可引用类型 T ,结果与 is_trivially_assignable_v<T&, T&&> 相同,否则为 false | T 应为完整类型、 cv void 或未知边界的数组 |
| template<class T> struct is_trivially_destructible; | is_destructible_v<T> true ,且 remove_all_extents_t<T> 是非类类型或具有平凡析构函数的类类型 | T 应为完整类型、 cv void 或未知边界的数组 |
| template<class T, class... Args> struct is_nothrow_constructible; | is_constructible_v<T, Args...> true ,且 is_constructible 的变量定义已知不抛出任何异常 | T 和所有 Args 类型应为完整类型、 cv void 或未知边界的数组 |
| template<class T> struct is_nothrow_default_constructible; | is_nothrow_constructible_v<T> true | T 应为完整类型、 cv void 或未知边界的数组 |
| template<class T> struct is_nothrow_copy_constructible; | 对于可引用类型 T ,结果与 is_nothrow_constructible_v<T, const T&> 相同,否则为 false | T 应为完整类型、 cv void 或未知边界的数组 |
| template<class T> struct is_nothrow_move_constructible; | 对于可引用类型 T ,结果与 is_nothrow_constructible_v<T, T&&> 相同,否则为 false | T 应为完整类型、 cv void 或未知边界的数组 |
| template<class T, class U> struct is_nothrow_assignable; | is_assignable_v<T, U> true ,且赋值操作已知不抛出任何异常 | T U 应为完整类型、 cv void 或未知边界的数组 |
| template<class T> struct is_nothrow_copy_assignable; | 对于可引用类型 T ,结果与 is_nothrow_assignable_v<T&, const T&> 相同,否则为 false | T 应为完整类型、 cv void 或未知边界的数组 |
| template<class T> struct is_nothrow_move_assignable; | 对于可引用类型 T ,结果与 is_nothrow_assignable_v<T&, T&&> 相同,否则为 false | T 应为完整类型、 cv void 或未知边界的数组 |
| template<class T, class U> struct is_nothrow_swappable_with; | is_swappable_with_v<T, U> true ,且 is_swappable_with<T, U> 定义中的每个交换表达式已知不抛出任何异常 | T U 应为完整类型、 cv void 或未知边界的数组 |
| template<class T> struct is_nothrow_swappable; | 对于可引用类型 T ,结果与 is_nothrow_swappable_with_v<T&, T&> 相同,否则为 false | T 应为完整类型、 cv void 或未知边界的数组 |
| template<class T> struct is_nothrow_destructible; | is_destructible_v<T> true ,且指定的析构函数已知不抛出任何异常 | T 应为完整类型、 cv void 或未知边界的数组 |
| template<class T> struct has_virtual_destructor; | T 有虚析构函数 | 若 T 是非联合类类型, T 应为完整类型 |
| template<class T> struct has_unique_object_representations; | 对于数组类型 T ,结果与 has_unique_object_representations_v<remove_all_extents_t<T>> 相同,否则见下文 | T 应为完整类型、 cv void 或未知边界的数组 |

以下是一些使用这些类型属性模板的示例:

#include <iostream>
#include <type_traits>

int main() {
    std::cout << std::boolalpha;
    std::cout << "is_const_v<const volatile int>: " << std::is_const_v<const volatile int> << std::endl;
    std::cout << "is_const_v<const int*>: " << std::is_const_v<const int*> << std::endl;
    std::cout << "is_const_v<const int&>: " << std::is_const_v<const int&> << std::endl;
    std::cout << "is_const_v<int[3]>: " << std::is_const_v<int[3]> << std::endl;
    std::cout << "is_const_v<const int[3]>: " << std::is_const_v<const int[3]> << std::endl;

    return 0;
}

在上述示例中,我们使用了 is_const_v 模板来判断不同类型是否为 const 限定的。

3. 类型属性查询

类型属性查询模板允许我们在编译时查询类型的属性,以下是类型属性查询模板及其值:
| 模板 | 值 |
| — | — |
| template<class T> struct alignment_of; | alignof(T) ,要求 alignof(T) 是有效的表达式 |
| template<class T> struct rank; | 若 T 是数组类型,返回 T 的维数;否则返回 0 |
| template<class T, unsigned I = 0> struct extent; | 若 T 不是数组类型,或其秩小于等于 I ,或 I 为 0 且 T 是 “未知边界的 U 数组” 类型,则返回 0;否则返回 T 的第 I 维的边界( I 从 0 开始索引) |

每个类型属性查询模板都是一个 Cpp17UnaryTypeTrait ,其基特性为 integral_constant<size_t, Value> 。例如:

#include <iostream>
#include <type_traits>

int main() {
    std::cout << "Alignment of int: " << std::alignment_of_v<int> << std::endl;
    std::cout << "Rank of int[3][4]: " << std::rank_v<int[3][4]> << std::endl;
    std::cout << "Extent of int[3][4] at dimension 0: " << std::extent_v<int[3][4], 0> << std::endl;
    std::cout << "Extent of int[3][4] at dimension 1: " << std::extent_v<int[3][4], 1> << std::endl;

    return 0;
}

在这个示例中,我们使用了 alignment_of_v rank_v extent_v 模板来查询 int 类型的对齐方式、二维数组 int[3][4] 的维数和每维的大小。

综上所述,C++ 类型特性模板为我们在编译时查询类型的属性和关系提供了强大的工具。通过合理使用这些模板,我们可以编写更加安全、高效的代码,避免运行时错误,提高程序的性能和可维护性。在实际编程中,我们可以根据具体需求选择合适的类型特性模板来实现所需的功能。

4. 类型关系模板

类型关系模板用于在编译时判断两个或多个类型之间的关系,下面详细介绍这些模板。

4.1 常用类型关系模板

以下是常见的类型关系模板及其作用:
| 模板 | 作用 |
| — | — |
| template<class T, class U> inline constexpr bool is_same_v = is_same<T, U>::value; | 判断 T U 是否为同一类型 |
| template<class Base, class Derived> inline constexpr bool is_base_of_v = is_base_of<Base, Derived>::value; | 判断 Base 是否为 Derived 的基类 |
| template<class From, class To> inline constexpr bool is_convertible_v = is_convertible<From, To>::value; | 判断 From 类型是否可以转换为 To 类型 |
| template<class From, class To> inline constexpr bool is_nothrow_convertible_v = is_nothrow_convertible<From, To>::value; | 判断 From 类型是否可以不抛出异常地转换为 To 类型 |
| template<class Fn, class... ArgTypes> inline constexpr bool is_invocable_v = is_invocable<Fn, ArgTypes...>::value; | 判断 Fn 是否可以用 ArgTypes... 作为参数进行调用 |
| template<class R, class Fn, class... ArgTypes> inline constexpr bool is_invocable_r_v = is_invocable_r<R, Fn, ArgTypes...>::value; | 判断 Fn 是否可以用 ArgTypes... 作为参数进行调用并返回 R 类型的值 |
| template<class Fn, class... ArgTypes> inline constexpr bool is_nothrow_invocable_v = is_nothrow_invocable<Fn, ArgTypes...>::value; | 判断 Fn 是否可以用 ArgTypes... 作为参数进行调用且不抛出异常 |
| template<class R, class Fn, class... ArgTypes> inline constexpr bool is_nothrow_invocable_r_v = is_nothrow_invocable_r<R, Fn, ArgTypes...>::value; | 判断 Fn 是否可以用 ArgTypes... 作为参数进行调用,不抛出异常并返回 R 类型的值 |

以下是使用这些模板的示例代码:

#include <iostream>
#include <type_traits>

struct Base {};
struct Derived : Base {};

void func(int) {}

int main() {
    std::cout << std::boolalpha;
    std::cout << "is_same_v<int, int>: " << std::is_same_v<int, int> << std::endl;
    std::cout << "is_base_of_v<Base, Derived>: " << std::is_base_of_v<Base, Derived> << std::endl;
    std::cout << "is_convertible_v<int, double>: " << std::is_convertible_v<int, double> << std::endl;
    std::cout << "is_invocable_v<decltype(func), int>: " << std::is_invocable_v<decltype(func), int> << std::endl;

    return 0;
}

在这个示例中,我们使用了 is_same_v 来判断两个类型是否相同, is_base_of_v 来判断继承关系, is_convertible_v 来判断类型转换, is_invocable_v 来判断函数调用的可行性。

5. 逻辑运算符特性模板

逻辑运算符特性模板用于对布尔类型的类型特性进行逻辑运算,包括逻辑与、逻辑或和逻辑非。

5.1 逻辑运算符特性模板介绍
模板 作用
template<class... B> inline constexpr bool conjunction_v = conjunction<B...>::value; 对多个布尔类型特性进行逻辑与运算
template<class... B> inline constexpr bool disjunction_v = disjunction<B...>::value; 对多个布尔类型特性进行逻辑或运算
template<class B> inline constexpr bool negation_v = negation<B>::value; 对一个布尔类型特性进行逻辑非运算

以下是使用这些模板的示例代码:

#include <iostream>
#include <type_traits>

struct TrueType : std::true_type {};
struct FalseType : std::false_type {};

int main() {
    std::cout << std::boolalpha;
    std::cout << "conjunction_v<TrueType, TrueType>: " << std::conjunction_v<TrueType, TrueType> << std::endl;
    std::cout << "disjunction_v<FalseType, TrueType>: " << std::disjunction_v<FalseType, TrueType> << std::endl;
    std::cout << "negation_v<FalseType>: " << std::negation_v<FalseType> << std::endl;

    return 0;
}

在这个示例中,我们定义了 TrueType FalseType 结构体,分别继承自 std::true_type std::false_type ,然后使用 conjunction_v disjunction_v negation_v 进行逻辑运算。

6. 常量求值上下文

在 C++ 中,有一个函数用于判断当前是否处于常量求值上下文:

constexpr bool is_constant_evaluated() noexcept;

这个函数返回一个布尔值,表示当前是否处于常量求值上下文。常量求值上下文通常用于在编译时进行一些计算,避免运行时开销。例如:

#include <iostream>

constexpr bool isConstEval() {
    if (std::is_constant_evaluated()) {
        return true;
    }
    return false;
}

constexpr int compileTimeValue = isConstEval() ? 10 : 20;

int main() {
    std::cout << "Compile time value: " << compileTimeValue << std::endl;
    return 0;
}

在这个示例中, isConstEval 函数使用 std::is_constant_evaluated() 来判断是否处于常量求值上下文,然后根据结果返回不同的值。 compileTimeValue 是一个常量表达式,它的值在编译时就确定了。

总结

C++ 的类型特性模板为开发者提供了强大的编译时类型查询和操作能力。通过使用这些模板,我们可以在编译时进行类型检查、类型转换判断、函数调用可行性判断等操作,从而提高代码的安全性和性能。

  • 辅助类 integral_constant 为各种类型特性提供了基础接口,方便我们定义不同的类型特性。
  • 一元类型特性 :包括主要类型类别、复合类型特性和类型属性模板,让我们可以在编译时查询类型的各种属性,如是否为 const 限定、是否为平凡类型等。
  • 类型属性查询 :允许我们在编译时查询类型的对齐方式、数组的维数和每维的大小等属性。
  • 类型关系模板 :帮助我们判断两个或多个类型之间的关系,如是否为同一类型、是否存在继承关系等。
  • 逻辑运算符特性模板 :对布尔类型的类型特性进行逻辑运算,增加了类型特性的灵活性。
  • 常量求值上下文 :让我们可以在编译时进行一些计算,避免运行时开销。

在实际编程中,我们应该根据具体需求合理使用这些类型特性模板,以提高代码的质量和可维护性。同时,要注意模板的前置条件和使用场景,避免因类型不完整或其他问题导致编译错误。

下面是一个简单的 mermaid 流程图,展示了使用类型特性模板的基本流程:

graph TD;
    A[确定需求] --> B[选择合适的类型特性模板];
    B --> C[检查前置条件];
    C -->|满足条件| D[使用模板进行编译时操作];
    C -->|不满足条件| E[调整类型或参数];
    E --> B;
    D --> F[编写业务代码];

这个流程图展示了在使用类型特性模板时的基本步骤:首先确定需求,然后选择合适的模板,检查前置条件,如果满足条件则进行编译时操作,不满足则调整类型或参数,最后编写业务代码。通过这样的流程,我们可以更加高效地利用类型特性模板。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值