 第20章 新的类型特性
第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> | 返回去掉引用、顶层 const和volatile的类型T如果 T是std::reference_wrapper<>(由std::ref()或std::cref()创建),则返回其包裹的类型,否则返回T如果 T是std::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
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
2
3
4
5
6
7
# 20.3 用于类型转换的新类型特性
# 20.3.1 std::remove_cvref_t<>
 std::remove_cvref_t<T>
返回去掉引用、顶层const或volatile的类型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*
2
# 20.3.2 std::unwrap_reference<>和std::unwrap_ref_decay_t
 std::unwrap_reference_t<T>
如果T是std::reference_wrapper<>(由std::ref()或std::cref()创建),则返回其包裹的类型,否则返回T。
std::unwrap_ref_decay_t<T>
如果T是std::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*
2
3
4
5
6
7
8
9
10
11
# 20.3.3 std::common_reference<>_t
 std::common_reference_t<T...>
返回所有类型T...的公共类型(如果存在),可以向该类型赋值。因此,对于给定的类型T1、T2和T3,该特性返回的类型应能接受这三种类型的值。理想情况下,它是一个引用类型。然而,如果涉及类型转换并创建了临时对象,那么它就是一个值类型。
例如:
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
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
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>  { };
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>;// 错误
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>;   				// 错误
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>;        // 错误
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>会判断类型T1和T2是否布局兼容,这样你就可以使用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
2
3
4
5
6
7
8
9
10
11
12
13
14
请注意,对于布局兼容性而言,仅仅类型和位大致匹配是不够的。根据语言规则:
- 有符号类型与无符号类型永远不会布局兼容。
- char甚至与- signed char和- unsigned 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
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
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 ;
2
该函数判断m1和m2是否分别指向S1和S2中布局兼容的成员。这意味着这些成员以及它们前面的所有成员都必须布局兼容。当且仅当S1和S2是标准布局类型,M1和M2是对象类型,并且m1和m2不为空时,该函数才返回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 (有符号与无符号对比)
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;
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
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,从而失败
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)中的提议被接受。
