第20章 新的类型特性

# 第20章 新的类型特性

本章介绍C++20在其标准库中引入的几个类型特性(以及两个针对类型的底层函数)。

表20.1列出了C++20的这些新类型特性(所有特性都在std命名空间中定义)。

特性 作用
std::is_bounded_array_v<T>
std::is_unbounded_array_v<T>
std::is_nothrow_convertible_v<T, T2>
std::is_layout_compatible_v<T1, T2>
std::is_pointer_interconvertible_base_of_v<BaseT, DerT>
如果类型T是已知大小的数组类型,则返回true
如果类型T是未知大小的数组类型,则返回true
如果T可以不抛出异常地转换为类型T2,则返回true
如果T1与类型T2在布局上兼容,则返回true
如果指向DerT的指针可以安全地转换为指向其基类型BaseT的指针,则返回true
std::remove_cvref_t<T>
std::unwrap_reference_t<T>
std::unwrap_ref_decay_t<T>
std::common_reference_t<T...>
std::type_identity_t<T>
std::iter_difference_t<T>
std::iter_value_t<T>
std::iter_reference_t<T>
std::iter_rvalue_reference_t<T>
返回去掉引用、顶层constvolatile的类型T
如果Tstd::reference_wrapper<>(由std::ref()std::cref()创建),则返回其包裹的类型,否则返回T
如果Tstd::reference_wrapper<>(由std::ref()std::cref()创建),则返回其包裹的类型,否则返回T的退化类型
返回所有类型T...的公共类型(如果存在),可以向该类型赋值
返回类型T本身
返回可递增/迭代器类型T的差值类型
返回指针/迭代器类型T的值/元素类型
返回指针/迭代器类型T的引用类型
返回指针/迭代器类型T的右值引用类型
std::is_clock_v<T>
std::compare_three_way_result_t<T>
如果T是时钟类型,则返回true
返回使用<=>运算符比较两个值的结果类型
表20.1 新的类型特性

以下各节将详细讨论这些特性,但以下特性除外:

  • std::is_clock_v<>在关于新的<chrono>特性的章节中讨论。
  • std::compare_three_way_result_t<>在关于新的三路比较的章节中讨论。

注意,此外,类型特性std::is_pod<>在C++20中已被弃用。

# 20.1 用于类型分类的新类型特性

# 20.1.1 std::is_bounded_array_v<>std::is_unbounded_array_v

std::is_bounded_array_v<T> std::is_unbounded_array_v<T>

用于判断类型T是否为有界/无界数组(大小已知/未知)。例如:

int  a[5];
std::is_bounded_array_v<decltype(a)>       // true
std::is_unbounded_array_v<decltype(a)>     // false

extern  int  b[];
std::is_bounded_array_v<decltype(b)>       // false
std::is_unbounded_array_v<decltype(b)>     // true
1
2
3
4
5
6
7

# 20.2 用于类型检查的新类型特性

# 20.2.1 std::is_nothrow_convertible_v<>

std::is_nothrow_convertible_v<From, To>

用于判断类型From是否可以保证不抛出任何异常地转换为类型To。例如:

// char* 转换为std::string:
std::is_convertible_v<char*,  std::string>                      // true
std::is_nothrow_convertible_v<char*,  std::string>              // false

// std::string转换为std::string_view:
std::is_convertible_v<std::string,  std::string_view>           // true
std::is_nothrow_convertible_v<std::string,  std::string_view>   // true
1
2
3
4
5
6
7

# 20.3 用于类型转换的新类型特性

# 20.3.1 std::remove_cvref_t<>

std::remove_cvref_t<T>

返回去掉引用、顶层constvolatile的类型T。表达式std::remove_cvref_t<T>等价于std::remove_cv_t<remove_reference_t<T>>。例如:

std::remove_cvref_t<const  std::string&>   // std::string
std::remove_cvref_t<const  char*  const>   // const char*
1
2

# 20.3.2 std::unwrap_reference<>std::unwrap_ref_decay_t

std::unwrap_reference_t<T>

如果Tstd::reference_wrapper<>(由std::ref()std::cref()创建),则返回其包裹的类型,否则返回T

std::unwrap_ref_decay_t<T>

如果Tstd::reference_wrapper<>(由std::ref()std::cref()创建),则返回其包裹的类型,否则返回T的退化类型。

例如:

std::unwrap_reference_t<decltype(std::ref(s))>     // std::string&
std::unwrap_reference_t<decltype(std::cref(s))>    // const  std::string&
std::unwrap_reference_t<decltype(s)>               // std::string
std::unwrap_reference_t<decltype(s)&>              // std::string&
std::unwrap_reference_t<int[4]>                    // int[4]

std::unwrap_ref_decay_t<decltype(std::ref(s))>     // std::string&
std::unwrap_ref_decay_t<decltype(std::cref(s))>    // const  std::string&
std::unwrap_ref_decay_t<decltype(s)>               // std::string
std::unwrap_ref_decay_t<decltype(s)&>              // std::string
std::unwrap_ref_decay_t<int[4]>                    // int*
1
2
3
4
5
6
7
8
9
10
11

# 20.3.3 std::common_reference<>_t

std::common_reference_t<T...>

返回所有类型T...的公共类型(如果存在),可以向该类型赋值。因此,对于给定的类型T1T2T3,该特性返回的类型应能接受这三种类型的值。理想情况下,它是一个引用类型。然而,如果涉及类型转换并创建了临时对象,那么它就是一个值类型。

例如:

std::common_reference_t<int&,  int>                                                       // int
std::common_reference_t<int&,  int&>                                                     // int&
std::common_reference_t<int&,  int&&>                                                   // const  int&
std::common_reference_t<int&&,  int&&>                                                 // int&&
std::common_reference_t<int&,  double>                                                 // double
std::common_reference_t<int&,  double&&>                                             // double
std::common_reference_t<char*,  std::string,  std::string_view> // std::string_view
std::common_reference_t<char,  std::string>                                       // ERROR
1
2
3
4
5
6
7
8

# 20.3.4 std::type_identity_t<>

std::type_identity_t<T>返回类型T本身。

这个类型特性有许多令人意想不到的用例:

  • 可以禁用使用参数来推导模板参数的功能。例如:
template<typename  T>
void  insert(std::vector<T>&  coll,  const  std::type_identity_t<T>&  value) {
    coll.push_back(value);
}

std::vector<double>  coll;
...
insert(coll,  42);  // 正确:42的类型不会用于推导类型T
1
2
3
4
5
6
7
8

如果参数value仅声明为const T&,编译器会报错,因为它会为类型T推导出两种不同的类型。

  • 可以将其用作构建块来定义返回类型的类型特性。例如,可以如下简单地定义一个去除const性的类型特性1
template<typename  T>
struct  remove_const  :  std::type_identity<T>  { };

template<typename  T>
struct  remove_const<const  T>  :  std::type_identity<T>  { };
1
2
3
4
5

# 20.4 迭代器的新型特性

本节列出了迭代器基本类型函数部分中提到的迭代器特性。

# 20.4.1 iter_difference_t<>

std::iter_difference_t<T>会产生与可递增/迭代器类型T相对应的差值类型。

这个特性专门用于处理间接可读类型的两个对象的值类型。与传统的迭代器特性(std::iterator_traits<>)不同,这个特性能够正确处理新的迭代器类别。

请注意,不存在对应的名为std::iter_difference且带有type成员的数据结构。相反,这个类型特性是通过尝试使用新的辅助类型std::incrementable_traits<>difference_type成员来定义的,其定义如下:

  • 如果有特化,就使用std::incrementable_traits<T>::difference_type
  • 对于原始指针,使用std::ptrdiff_t
  • 否则,如果已定义,使用T::difference_type
  • 否则,使用两个T之间差值的有符号整数差值类型。
  • 对于const T,使用T的差值类型。

例如:

using  T1  =  std::iter_difference_t<int*>;                    // std::ptrdiff_t
using  T2  =  std::iter_difference_t<std::string>;             // std::ptrdiff_t
using  T3  =  std::iter_difference_t<std::vector<long>>;       // std::ptrdiff_t
using  T4  =  std::iter_difference_t<int>;                                             // int
using  T5  =  std::iter_difference_t<std::chrono::sys_seconds>;// 错误
1
2
3
4
5

# 20.4.2 iter_value_t<>

std::iter_value_t<T>会产生与指针/迭代器类型T相对应的非const值/元素类型。

这个特性专门用于处理间接可读类型的值类型。与传统的迭代器特性(std::iterator_traits<>)不同,这个特性能够正确处理新的迭代器类别。

请注意,不存在对应的名为std::iter_value且带有type成员的数据结构。相反,这个类型特性是通过尝试使用新的辅助类型std::indirectly_readable_traits<>value_type成员来定义的,其定义如下:

  • 如果有特化,就使用std::indirectly_readable_traits<T>::value_type
  • 对于原始指针,使用它所指向的非const/volatile类型。
  • 否则,如果已定义,使用remove_cv_t<T::value_type>
  • 否则,如果已定义,使用remove_cv_t<T::element_type>
  • 对于const T,使用T的值类型。

例如:

using  T1  =  std::iter_value_t<int*>;                  // int
using  T2  =  std::iter_value_t<const  int*  const>;    // int
using  T3  =  std::iter_value_t<std::string>;           // char
using  T4  =  std::iter_value_t<std::vector<long>>;     // long
using  T5  =  std::iter_value_t<int>;   				// 错误
1
2
3
4
5

# 20.4.3 iter_reference_t<>和iter_rvalue_reference_t<>

std::iter_reference_t<T>会产生与可解引用的指针/迭代器类型T相对应的左值引用类型。它等同于decltype(*declval<T&>())

std::iter_rvalue_reference_t<T>会产生与可解引用的指针/迭代器类型T相对应的右值引用类型。它等同于decltype(std::ranges::iter_move(declval<T&>()))

这些特性专门用于处理间接可写类型的值类型。与传统的迭代器特性(std::iterator_traits<>)不同,这些特性能够正确处理新的迭代器类别。

请注意,不存在对应的带有type成员的std::iter_value数据结构。相反,这些特性直接按照上述描述进行定义。

例如:

using  T1  =  std::iter_reference_t<int*>;                      // int&
using  T2  =  std::iter_reference_t<const  int*  const>;        // const  int&
using  T3  =  std::iter_reference_t<std::string>;               // 错误
using  T4  =  std::iter_reference_t<std::vector<long>>;         // 错误
using  T5  =  std::iter_reference_t<int>;   					// 错误
using  T6  =  std::iter_rvalue_reference_t<int*>;               // int&&
using  T7  =  std::iter_rvalue_reference_t<const  int*  const>; // const  int&&
using  T8  =  std::iter_rvalue_reference_t<std::string>;        // 错误
1
2
3
4
5
6
7
8

# 20.5 用于布局兼容性的类型特性和函数

在许多情况下,了解两种类型或指向类型的指针是否可以安全地相互转换非常重要。C++标准使用“布局兼容(layout-compatible)”这一术语来描述这种情况。

为了检查类成员之间的布局兼容关系,C++20还引入了两个新的普通函数。它们的优点是可以在运行时上下文中使用。

# 20.5.1 is_layout_compatible_v<>

std::is_layout_compatible_v<T1 , T2>会判断类型T1T2是否布局兼容,这样你就可以使用reinterpret_cast安全地将指向它们的指针进行转换。

例如:

struct  Data  { 
    int  i;
    const  std::string  s; 
};

class  Type  { 
private:
    const  int  id  =  nextId();
    std::string  name;
public:
   ...
};

std::is_layout_compatible_v<Data,  Type> // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14

请注意,对于布局兼容性而言,仅仅类型和位大致匹配是不够的。根据语言规则:

  • 有符号类型与无符号类型永远不会布局兼容。
  • char甚至与signed charunsigned char都永远不会布局兼容。
  • 引用与非引用永远不会布局兼容。
  • 不同(即使布局兼容)类型的数组永远不会布局兼容。
  • 枚举类型与其底层类型永远不会布局兼容。

例如:

enum  class  E  {};
enum  class  F  :  int  {};

std::is_layout_compatible_v<E,  F>                    // true
std::is_layout_compatible_v<E[2],  F[2]>              // false
std::is_layout_compatible_v<E,  int>                  // false
std::is_layout_compatible_v<char ,  char>             // true
std::is_layout_compatible_v<char ,  signed  char>     // false
std::is_layout_compatible_v<char ,  unsigned  char>   // false
std::is_layout_compatible_v<char ,  char&>            // false
1
2
3
4
5
6
7
8
9
10

# 20.5.2 is_pointer_interconvertible_base_of_v<>

std::is_pointer_interconvertible_base_of<Base , Der>判断指向类型Der的指针是否可以使用reinterpret_cast安全地转换为指向其基类型Base的指针,如果可以则返回true

如果两者具有相同的类型,这个特性总是返回true。例如:

struct  B1  {  };
struct  D1  :  B1  {  int  x;  };
struct  B2  {  int  x;  };
struct  D2  :  B2  {  int  y;  };   // 不是标准布局类型

std::is_pointer_interconvertible_base_of_v<B1,  D1>     // true
std::is_pointer_interconvertible_base_of_v<B2,  D2>     // false
1
2
3
4
5
6
7

指向D2的指针不能安全地转换为指向B2的指针,原因是D2不是标准布局类型,因为并非所有成员都在同一类中以相同的访问权限定义。

# 20.5.3 is_corresponding_member()

template<typename  S1,  typename  S2,  typename  M1,  typename  M2>
constexpr  bool  is_corresponding_member(M1  S1::*m1,  M2  S2::*m2)  noexcept ;
1
2

该函数判断m1m2是否分别指向S1S2中布局兼容的成员。这意味着这些成员以及它们前面的所有成员都必须布局兼容。当且仅当S1S2是标准布局类型,M1M2是对象类型,并且m1m2不为空时,该函数才返回true

例如:

struct  Point2D  {  int  a;  int  b;  };
struct  Point3D  {  int  x;  int  y;  int  z;  };
struct  Type1  {  const  int  id;  int  val;  std::string  name;  }; 
struct  Type2  {  unsigned  int  id;  int  val;  };

std::is_corresponding_member(&Point2D::b,  &Point3D::y)   // true
std::is_corresponding_member(&Point2D::b,  &Point3D::z)   // false (第二个与第三个int对比)
std::is_corresponding_member(&Point2D::b,  &Type1::val)   // true
std::is_corresponding_member(&Point2D::b,  &Type2::val)   // false (有符号与无符号对比)
1
2
3
4
5
6
7
8
9

# 20.5.4 is_pointer_interconvertible_with_class()

template<typename S, typename M>
constexpr bool is_pointer_interconvertible_with_class(M S::*m ) noexcept;
1
2

该函数用于判断类型为S的每个对象s是否与它的子对象s.*m指针可相互转换。当且仅当S是标准布局类型(standard-layout type),M是对象类型,并且m不为空时,函数返回true

例如:

struct B1 { int x; };
struct B2 { int y; };
struct DB1 : B1 {};
struct DB1B2 : B1, B2 {};   // 不是标准布局类型

std::is_pointer_interconvertible_with_class<B1, int>(&DB1::x)          // true
std::is_pointer_interconvertible_with_class<DB1, int>(&B1::x)          // true
std::is_pointer_interconvertible_with_class<DB1, int>(&DB1::x)         // true
std::is_pointer_interconvertible_with_class<B1, int>(&DB1B2::x)        // true
std::is_pointer_interconvertible_with_class<DB1B2, int>(&B1::x)        // false
std::is_pointer_interconvertible_with_class<DB1B2, int>(&DB1B2::x)     // false
1
2
3
4
5
6
7
8
9
10
11

C++20标准在一条注释中解释了最后两个表达式为false的原因: 指向成员表达式&C::b的类型并不总是指向C的成员的指针。这在结合继承使用这些函数时可能会导致出人意料的结果:

struct A { int a; };
struct B { int b; };
struct C: public A, public B { };
struct A { int a; }; 				// 标准布局类
struct B { int b; }; 				// 标准布局类
struct C: public A, public B { }; 	// 不是标准布局类

std::is_pointer_interconvertible_with_class(&C::b)      // true
// true因为,尽管表面上如此,&C::b的类型是
// “指向B中int类型成员的指针”
std::is_pointer_interconvertible_with_class<C>(&C::b)   // false
// false因为它强制使用类C,从而失败
1
2
3
4
5
6
7
8
9

# 20.6 补充说明

类型特性is_bounded_array<>is_unbounded_array<>按照沃尔特·E·布朗(Walter E. Brown)和格伦·J·费尔南德斯(Glen J. Fernandes)在http://wg21.link/p1357r1 (opens new window)中的提议被接受。

类型特性is_nothrow_convertible<>按照丹尼尔·克尔(Daniel Kr)在http://wg21.link/p0758r1 (opens new window)中的提议被接受。

类型特性common_reference<>作为范围库(ranges library)的一部分,由埃里克·尼布勒(Eric Niebler)、凯西·卡特(Casey Carter)和克里斯托弗·迪·贝拉(Christopher Di Bella)在http://wg21.link/p0896r4 (opens new window)中提议并被接受。

类型特性unwrap_reference<>unwrap_ref_decay<>按照比森特·J·博特·埃斯克里瓦(Vicente J. Botet Escriba)在http://wg21.link/p0318r1 (opens new window)中的提议被接受。

类型特性remove_cvref<>按照沃尔特·E·布朗(Walter E. Brown)在http://wg21.link/p0550r2 (opens new window)中的提议被接受。

类型特性type_identity<>按照蒂穆尔·杜姆勒(Timur Doumler)在http://wg21.link/p0887r1 (opens new window)中的提议被接受。

迭代器类型特性作为采用范围库提议的一部分,由埃里克·尼布勒(Eric Niebler)、凯西·卡特(Casey Carter)和克里斯托弗·迪·贝拉(Christopher Di Bella)在http://wg21.link/p0896r4 (opens new window)中提议并被接受。

类型特性is_layout_compatible<>is_pointer_interconvertible_base_of<>,以及函数is_corresponding_member()is_pointer_interconvertible_with_class()按照丽莎·利平科特(Lisa Lippincott)在http://wg21.link/p0466r5 (opens new window)中的提议被接受。

上次更新: 2025/03/20, 19:44:38

社区交流

讨论与留言

前往 GitHub Issues →

本留言区仅对应当前文章,欢迎补充观点、提出问题或帮助修正文中疏漏。 留言由 GitHub/Gitalk 提供,需要使用 GitHub 登录。

最近更新
第 1 课:专栏导论与安全边界
07-01
第 3 课:安装编译与调试环境
07-01
第 4 课:第三方库下载、编译与依赖管理
07-01
更多文章>