第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)中的提议被接受。