第5章 标准概念详解
# 第5章 标准概念详解
本章详细介绍C++20标准库中的所有概念。
# 5.1 所有标准概念概述
“类型和对象的基本概念”表列出了一般类型和对象的基本概念。“范围、迭代器和算法的概念”表列出了范围、视图、迭代器和算法相关的概念。“辅助概念”表列出了主要用作其他概念构建块的概念,应用程序员通常不会直接使用这些概念。
# 5.1.1 头文件和命名空间
标准概念在不同的头文件中定义:
- 许多基本概念在头文件
<concepts>
中定义,<ranges>
和<iterator>
会包含该头文件。 - 迭代器相关的概念在头文件
<iterator>
中定义。 - 范围相关的概念在头文件
<ranges>
中定义。 three_way_comparable
相关概念在<compare>
中定义(几乎其他每个头文件都会包含它)。uniform_random_bit_generator
在<random>
中定义。
几乎所有概念都在命名空间std
中定义。唯一的例外是范围相关的概念,它们在命名空间std::ranges
中定义。
概念 | 约束 |
---|---|
integral signed_integral unsigned_integral floating_point | 整型类型 有符号整型类型 无符号整型类型 浮点型类型 |
movable copyable semiregular regular | 支持移动初始化/赋值和交换 支持移动和复制初始化/赋值以及交换 支持默认初始化、复制、移动和交换 支持默认初始化、复制、移动、交换以及相等性比较 |
same_as convertible_to derived_from constructible_from assignable_from swappable_with common_with common_reference_with | 相同类型 可转换为另一种类型的类型 从另一种类型派生的类型 可由其他类型构造的类型 可从另一种类型赋值的类型 可与另一种类型交换的类型 两种类型有共同类型 两种类型有共同引用类型 |
equality_comparable equality_comparable_with totally_ordered totally_ordered_with three_way_comparable three_way_comparable_with | 类型支持相等性检查 可以检查两种类型是否相等 类型支持严格弱序关系 可以检查两种类型是否满足严格弱序关系 可以应用所有比较运算符(包括 <=> 运算符)可以用所有比较运算符(包括 <=> )比较两种类型 |
invocable regular_invocable predicate relation equivalence_relation strict_weak_order uniform_random_bit_generator | 类型对于指定参数是可调用的 类型对于指定参数是可调用的(无修改) 类型是一个谓词(可调用且返回布尔值) 可调用类型定义了两种类型之间的关系 可调用类型定义了两种类型之间的相等关系 可调用类型定义了两种类型之间的顺序关系 可调用类型可用作随机数生成器 |
表5.1 类型和对象的基本概念
概念 | 约束 |
---|---|
default_initializable move_constructible copy_constructible destructible swappable weakly_incrementable incrementable | 类型是可默认初始化的 类型支持移动初始化 类型支持复制初始化 类型是可销毁的 类型是可交换的 类型支持递增运算符 类型支持保持相等性的递增运算符 |
表5.2 辅助概念
概念 | 约束 |
---|---|
range output_range input_range forward_range bidirectional_range random_access_range contiguous_range sized_range common_range borrowed_range view viewable_range | 类型是一个范围 类型是一个可写入的范围 类型是一个可读的范围 类型是一个可多次读取的范围 类型是一个可向前和向后读取的范围 类型是一个支持在元素间跳跃访问的范围 类型是一个元素在连续内存中的范围 类型是一个支持快速获取大小的范围 类型是一个迭代器和哨兵类型相同的范围 类型是一个左值或借用范围 类型是一个视图 类型是或可转换为一个视图 |
indirectly_writable indirectly_readable indirectly_movable indirectly_movable_storable indirectly_copyable indirectly_copyable_storable indirectly_swappable indirectly_comparable | 类型可用于写入其引用的位置 类型可用于读取其引用的位置 类型引用的对象是可移动的 类型引用的对象是可移动的且支持临时对象 类型引用的对象是可复制的 类型引用的对象是可复制的且支持临时对象 类型引用的对象是可交换的 类型引用的对象是可比较的 |
input_output_iterator output_iterator input_iterator forward_iterator bidirectional_iterator random_access_iterator contiguous_iterator sentinel_for sized_sentinel_for | 类型是一个迭代器 类型是一个输出迭代器 类型是(至少)一个输入迭代器 类型是(至少)一个前向迭代器 类型是(至少)一个双向迭代器 类型是(至少)一个随机访问迭代器 类型是指向连续内存中元素的迭代器 类型可用作某迭代器类型的哨兵 类型可用作某迭代器类型的哨兵且能快速计算距离 |
permutable mergeable sortable | 类型是(至少)一个可对元素重新排序的前向迭代器 两种类型可用于将已排序元素合并到第三种类型中 类型是可排序的(根据比较和投影) |
indirectly_unary_invocable indirectly_regular_unary_invocable indirect_unary_predicate indirect_binary_predicate indirect_equivalence_relation indirect_strict_weak_order | 操作可使用迭代器的值类型进行调用 无状态操作可使用迭代器的值类型进行调用 一元谓词可使用迭代器的值类型进行调用 二元谓词可使用两个迭代器的值类型进行调用 谓词可用于检查传递的迭代器的两个值是否相等 谓词可用于对传递的迭代器的两个值进行排序 |
表5.3 范围、迭代器和算法的概念
# 5.1.2 标准概念的包含关系
C++标准库提供的概念经过精心设计,在合理的情况下会包含其他概念。实际上,它们构建了一个相当复杂的包含关系图。图5.1展示了其复杂程度。
图5.1 C++标准概念的包含关系图(节选)
因此,概念的描述中会列出它包含了哪些其他关键概念。
# 5.2 与语言相关的概念
本节列出适用于一般对象和类型的概念。
# 5.2.1 算术概念
# std::integral<T>
- 保证类型
T
是整型(包括bool
和所有字符类型)。 - 要求:
- 类型特性
std::is_integral_v<T>
为true
。
- 类型特性
# std::signed_integral<T>
- 保证类型
T
是有符号整型(包括有符号字符类型,char
可能属于此类)。 - 要求:
- 满足
std::integral<T>
。 - 类型特性
std::is_signed_v<T>
为true
。
- 满足
# std::unsigned_integral<T>
- 保证类型
T
是无符号整型(包括bool
和无符号字符类型,char
可能属于此类)。 - 要求:
- 满足
std::integral<T>
。 - 不满足
std::signed_integral<T>
。
- 满足
# std::floating_point<T>
- 保证类型
T
是浮点型(float
、double
或long double
)。 - 引入此概念是为了能够定义数学常量。
- 要求:
- 类型特性
std::is_floating_point_v<T>
为true
。
- 类型特性
# 5.2.2 对象概念
对于对象(既不是引用、也不是函数、更不是void
的类型),存在一系列所需基本操作的层级关系。
# std::movable<T>
保证类型
T
是可移动且可交换的。也就是说,可以进行移动构造、移动赋值,并且能与同类型的另一个对象进行交换。要求:
- 满足
std::move_constructible<T>
。 - 满足
std::assignable_from<T&, T>
。 - 满足
std::swappable<T>
。 T
既不是引用,也不是函数,亦不是void
。
- 满足
std::copyable<T>
- 保证:类型
T
是可复制的(这意味着它是可移动且可交换的)。 - 要求:
- 满足
std::movable<T>
。 - 满足
std::copy_constructible<T>
。 - 对于任意的
T
、T&
、const T
和const T&
到T&
,满足std::assignable_from
。 - 满足
std::swappable<T>
。 T
既不是引用,也不是函数,亦不是void
。
- 满足
- 保证:类型
std::semiregular<T>
- 保证:类型
T
是半规则的(可以默认初始化、复制、移动和交换)。 - 要求:
- 满足
std::copyable<T>
。 - 满足
std::default_initializable<T>
。 - 满足
std::movable<T>
。 - 满足
std::copy_constructible<T>
。 - 对于任意的
T
、T&
、const T
和const T&
到T&
,满足std::assignable_from
。 - 满足
std::swappable<T>
。 T
既不是引用,也不是函数,亦不是void
。
- 满足
- 保证:类型
std::regular<T>
- 保证:类型
T
是规则的(可以默认初始化、复制、移动、交换,并进行相等性检查)。 - 要求:
- 满足
std::semiregular<T>
。 - 满足
std::equality_comparable<T>
。 - 满足
std::copyable<T>
。 - 满足
std::default_initializable<T>
。 - 满足
std::movable<T>
。 - 满足
std::copy_constructible<T>
。 - 对于任意的
T
、T&
、const T
和const T&
到T&
,满足std::assignable_from
。 - 满足
std::swappable<T>
。 T
既不是引用,也不是函数,亦不是void
。
- 满足
- 保证:类型
# 5.2.3 类型间关系的概念
std::same_as<T1, T2>
- 保证:类型
T1
和T2
相同。 - 该概念两次调用
std::is_same_v
类型特性,以确保参数顺序无关紧要。 - 要求:
- 类型特性
std::is_same_v<T1, T2>
为真。 T1
和T2
的顺序无关紧要。
- 类型特性
- 保证:类型
std::convertible_to<From, To>
- 保证:类型
From
的对象既可以隐式转换,也可以显式转换为类型To
的对象。 - 要求:
- 类型特性
std::is_convertible_v<From, To>
为真。 - 支持从
From
到To
的static_cast
转换。 - 可以将类型
From
的对象作为To
返回。
- 类型特性
- 保证:类型
std::derived_from<D, B>
- 保证:类型
D
是从类型B
公开派生的(或者D
和B
相同),这样任何类型为D
的指针都可以转换为类型为B
的指针。换句话说,该概念保证D
的引用/指针可以当作B
的引用/指针使用。 - 要求:
- 类型特性
std::is_base_of_v<B, D>
为真。 - 类型特性
std::is_convertible_v
对于类型为D
的常量指针到B
的转换为真。
- 类型特性
- 保证:类型
std::constructible_from<T, Args...>
- 保证:可以使用类型为
Args...
的参数初始化类型为T
的对象。 - 要求:
- 满足
std::destructible<T>
。 - 类型特性
std::is_constructible_v<T, Args...>
为真。
- 满足
- 保证:可以使用类型为
std::assignable_from<To, From>
- 保证:可以将类型
From
的值移动赋值或复制赋值给类型To
的值。赋值操作还必须返回原来的To
对象。 - 要求:
To
必须是左值引用。- 对于类型的常量左值引用,满足
std::common_reference_with<To, From>
。 - 必须支持
operator =
,并且返回类型与To
相同。
- 保证:可以将类型
std::swappable_with<T1, T2>
- 保证:类型
T1
和T2
的值可以交换。 - 要求:
- 满足
std::common_reference_with<T1, T2>
。 - 任意两个类型为
T1
和T2
的对象可以使用std::ranges::swap()
相互交换值。
- 满足
- 保证:类型
std::common_with<T1, T2>
- 保证:类型
T1
和T2
共享一个可以显式转换到的公共类型。 - 要求:
- 类型特性
std::common_type_t<T1, T2>
产生一个类型。 - 支持转换到它们的公共类型的
static_cast
。 - 两种类型的引用共享一个
common_reference
类型。 T1
和T2
的顺序无关紧要。
- 类型特性
- 保证:类型
std::common_reference_with<T1, T2>
- 保证:类型
T1
和T2
共享一个common_reference
类型,可以隐式和显式转换到该类型。 - 要求:
- 类型特性
std::common_reference_t<T1, T2>
产生一个类型。 - 两种类型都可以转换为它们的公共引用类型。
T1
和T2
的顺序无关紧要。
- 类型特性
- 保证:类型
# 5.2.4 比较概念
std::equality_comparable<T>
- 保证:类型
T
的对象可以使用==
和!=
运算符进行比较。比较顺序无关紧要。 T
的operator ==
应该是对称且传递的:t1 == t2
为真当且仅当t2 == t1
为真。- 如果
t1 == t2
和t2 == t3
为真,那么t1 == t3
为真。然而,这是一个语义约束,无法在编译时检查。
- 要求:
- 支持
==
和!=
,并且返回一个可以转换为bool
的值。
- 支持
- 保证:类型
std::equality_comparable_with<T1, T2>
- 保证:类型
T1
和T2
的对象可以使用==
和!=
运算符进行比较。 - 要求:
- 对于所有涉及
T1
和 / 或T2
对象的比较,都支持==
和!=
,并且返回相同类型且可转换为bool
的值。
- 对于所有涉及
- 保证:类型
std::totally_ordered<T>
- 保证:类型
T
的对象可以使用==
、!=
、<
、<=
、>
和>=
运算符进行比较,这样两个值总是相等、小于或大于彼此。 - 该概念并不要求类型
T
对所有值都有全序关系。实际上,即使浮点值没有全序关系,表达式std::totally_ordered<double>
也会返回true
:
- 保证:类型
std::totally_ordered<double> // true
std::totally_ordered<std::pair<double, double>> // true
std::totally_ordered<std::complex<int>> // false
2
3
因此,这个概念用于检查进行元素排序所需的形式要求。它被std::ranges::less
使用,std::ranges::less
是排序算法的默认排序准则。这样,如果类型对所有值没有全序关系,排序算法也不会编译失败。支持所有六个基本比较运算符就足够了。要排序的值应该具有全序或弱序关系。然而,这是一个语义约束,无法在编译时检查。
- 要求:
- 满足std::equality_comparable<T>
。
- 所有使用==
、!=
、<
、<=
、>
和>=
运算符的比较都返回一个可以转换为bool
的值。
std::totally_ordered_with<T1, T2>
- 保证:类型
T1
和T2
的对象可以使用==
、!=
、<
、<=
、>
和>=
运算符进行比较,这样两个值总是相等、小于或大于彼此。 - 要求:
- 对于所有涉及
T1
和 / 或T2
对象的比较,都支持==
、!=
、<
、<=
、>
和>=
,并且返回相同类型且可转换为bool
的值。
- 对于所有涉及
- 保证:类型
std::three_way_comparable<T>
、std::three_way_comparable<T, Cat>
- 保证:类型
T
的对象可以使用==
、!=
、<
、<=
、>
、>=
和<=>
运算符进行比较(并且至少具有比较类别类型Cat
)。如果没有传递Cat
,则要求为std::partial_ordering
。 - 这个概念在头文件
<compare>
中定义。 - 注意,这个概念并不意味着也不涵盖
std::equality_comparable
,因为后者要求只有当两个对象相等时operator ==
才返回true
。在弱序或偏序关系中,情况可能并非如此。 - 注意,这个概念并不意味着也不涵盖
std::totally_ordered
,因为后者要求比较类别是std::strong_ordering
或std::weak_ordering
。 - 要求:
- 所有使用
==
、!=
、<
、<=
、>
和>=
运算符的比较都返回一个可以转换为bool
的值。 - 任何使用
<=>
运算符的比较都返回一个比较类别(至少是Cat
)。
- 所有使用
- 保证:类型
std::three_way_comparable_with<T1, T2>
、std::three_way_comparable_with<T1, T2, Cat>
- 保证:任意两个类型为
T1
和T2
的对象可以使用==
、!=
、<
、<=
、>
、>=
和<=>
运算符进行比较(并且至少具有比较类别类型Cat
)。如果没有传递Cat
,则要求为std::partial_ordering
。 - 这个概念在头文件
<compare>
中定义。 - 注意,这个概念并不意味着也不涵盖
std::equality_comparable_with
,因为后者要求只有当两个对象相等时operator ==
才返回true
。在弱序或偏序关系中,情况可能并非如此。 - 注意,这个概念并不意味着也不涵盖
std::totally_ordered_with
,因为后者要求比较类别是std::strong_ordering
或std::weak_ordering
。 - 要求:
- 对于
T1
和T2
(以及Cat
)的值和公共引用,满足std::three_way_comparable
。 - 所有使用
==
、!=
、<
、<=
、>
和>=
运算符的比较都返回一个可以转换为bool
的值。 - 任何使用
<=>
运算符的比较都返回一个比较类别(至少是Cat
)。 T1
和T2
的顺序无关紧要。
- 对于
- 保证:任意两个类型为
# 5.3 迭代器和范围的概念
本节列出了迭代器和范围的所有基本概念,这些概念在算法和类似函数中很有用。
注意,范围相关的概念是在命名空间std::ranges
中提供的,而不是在std
命名空间中。它们在头文件<ranges>
中声明。
迭代器相关的概念在头文件<iterator>
中声明。
# 5.3.1 范围和视图的概念
定义了几个概念来约束范围。它们与迭代器的概念相对应,并且考虑到自C++20以来有了新的迭代器类别。
std::ranges::range<Rg>
- 保证:
Rg
是一个有效的范围。 - 这意味着类型为
Rg
的对象支持通过使用std::ranges::begin()
和std::ranges::end()
对元素进行迭代。如果范围是数组,或者提供begin()
和end()
成员函数,又或者可以与独立的begin()
和end()
函数一起使用,就满足这个条件。
- 保证:
此外,对于
std::ranges::begin()
和std::ranges::end()
,有以下约束:- 它们必须在(均摊)常数时间内操作。
- 它们不会修改范围。
- 除非范围至少没有提供前向迭代器,否则多次调用
begin()
会得到相同的位置。
这意味着我们可以高效地遍历所有元素(除非只有纯输入迭代器,否则甚至可以多次遍历)。
- 要求:
- 对于类型为
Rg
的对象rg
,支持std::ranges::begin(rg)
且支持std::ranges::end(rg)
。 std::ranges::output_range<Rg, T>
- 保证
Rg
是一个范围,它至少提供输出迭代器(可用于写入的迭代器),这些迭代器可以接受类型为T
的值。 - 要求:
std::range<Rg>
满足。- 迭代器类型和
T
满足std::output_iterator
。
- 保证
std::ranges::input_range<Rg>
- 保证
Rg
是一个范围,它至少提供输入迭代器(可用于读取的迭代器)。 - 要求:
std::range<Rg>
满足。- 迭代器类型满足
std::input_iterator
。
- 保证
std::ranges::forward_range<Rg>
- 保证
Rg
是一个范围,它至少提供前向迭代器(可用于读取、写入和多次遍历的迭代器)。 - 注意,迭代器的
iterator_category
成员可能不匹配。对于生成纯右值(prvalue)的迭代器,如果可用,其iterator_category
为std::input_iterator_tag
。 - 要求:
std::input_range<Rg>
满足。- 迭代器类型满足
std::forward_iterator
。
- 保证
std::ranges::bidirectional_range<Rg>
- 保证
Rg
是一个范围,它至少提供双向迭代器(可用于读取、写入以及反向遍历的迭代器)。 - 注意,迭代器的
iterator_category
成员可能不匹配。对于生成纯右值的迭代器,如果可用,其iterator_category
为std::input_iterator_tag
。 - 要求:
std::forward_range<Rg>
满足。- 迭代器类型满足
std::bidirectional_iterator
。
- 保证
std::ranges::random_access_range<Rg>
- 保证
Rg
是一个范围,它提供随机访问迭代器(可用于读取、写入、前后跳转以及计算距离的迭代器)。 - 注意,迭代器的
iterator_category
成员可能不匹配。对于生成纯右值的迭代器,如果可用,其iterator_category
为std::input_iterator_tag
。 - 要求:
std::bidirectional_range<Rg>
满足。- 迭代器类型满足
std::random_access_iterator
。
- 保证
std::ranges::contiguous_range<Rg>
- 保证
Rg
是一个范围,它提供随机访问迭代器,并且额外要求元素存储在连续内存中。 - 注意,迭代器的
iterator_category
成员不匹配。如果定义了该类别成员,它只能是std::random_access_iterator_tag
,如果值是纯右值,甚至只能是std::input_iterator_tag
。 - 要求:
std::random_access_range<Rg>
满足。- 迭代器类型满足
std::contiguous_iterator
。 - 调用
std::ranges::data()
会返回指向第一个元素的原始指针。
- 保证
std::ranges::sized_range<Rg>
- 保证
Rg
是一个范围,其中元素的数量可以在常数时间内计算出来(可以通过调用成员函数size()
,或者计算起始和结束位置的差值)。 - 如果满足这个概念,对于类型为
Rg
的对象,std::ranges::size()
是快速且定义明确的。 - 注意,这个概念的性能方面是一个语义约束,在编译时无法检查。如果一个类型虽然提供了
size()
,但并不满足这个概念,可以定义std::disable_sized_range<Rg>
为true
来表明这一点。 - 要求:
std::range<Rg>
满足。- 支持调用
std::ranges::size()
。
- 保证
std::ranges::common_range<Rg>
- 保证
Rg
是一个范围,其中起始迭代器和哨兵(结束迭代器)具有相同的类型。 - 以下情况总是满足这个保证:
- 所有标准容器(
vector
、list
等)。 empty_view
。single_view
。common_view
。
- 所有标准容器(
- 以下情况不满足这个保证:
take views
。const drop views
。- 没有结束值或结束值类型不同的
iota views
。 - 对于其他视图,这取决于底层范围的类型。
- 要求:
std::range<Rg>
满足。std::ranges::iterator_t<Rg>
和std::ranges::sentinel_t<Rg>
具有相同的类型。
- 保证
std::ranges::borrowed_range<Rg>
- 保证在当前上下文中传递的范围
Rg
生成的迭代器,即使在范围不再存在时也能使用。如果传递的是左值,或者传递的范围始终是借用范围,那么这个概念就满足。 - 如果满足这个概念,范围的迭代器就不依赖于范围的生命周期。这意味着当创建迭代器的范围被销毁时,迭代器不会悬空。但是,如果范围的迭代器引用了底层范围,而底层范围不再存在,迭代器仍然可能悬空。
- 形式上,如果
Rg
是一个左值(例如有名称的对象),或者变量模板std::ranges::enable_borrowed_range<Rg>
为true
,则满足这个概念,以下视图属于这种情况:subrange
、ref_view
、string_view
、span
、iota_view
和empty_view
。 - 要求:
std::range<Rg>
满足。Rg
是左值或者enable_borrowed_range<Rg>
为true
。
- 保证在当前上下文中传递的范围
std::ranges::view<Rg>
- 保证
Rg
是一个视图(一个复制、移动、赋值和销毁成本都很低的范围)。 - 一个视图有以下要求:
- 它必须是一个范围(支持遍历元素)。
- 它必须是可移动的。
- 移动构造函数,如果有复制构造函数的话也包括复制构造函数,必须具有常数复杂度。
- 移动赋值运算符,如果有复制赋值运算符的话也包括复制赋值运算符,必须成本很低(常数复杂度或者不比销毁加创建的成本更差)。
- 除了最后一个要求,其他要求都由相应的概念检查。最后一个要求是一个语义约束,类型的实现者必须通过从
std::ranges::view_interface
公开派生,或者特化std::ranges::enable_view<Rg>
为true
来保证。
- 要求:
std::range<Rg>
满足。std::movable<Rg>
满足。- 变量模板
std::ranges::enable_view<Rg>
为true
。
- 保证
std::ranges::viewable_range<Rg>
- 保证
Rg
是一个范围,可以使用std::views::all()
适配器安全地转换为视图。 - 如果
Rg
已经是一个视图,或者是范围的左值,或者是可移动的右值范围,但不是初始化列表,那么这个概念就满足。 - 要求:
std::range<Rg>
满足。
- 保证
- 对于类型为
# 5.3.2 针对类似指针对象的概念
本节列出了所有标准概念,这些概念用于那些可以使用*
运算符来处理其所指向值的对象。这通常适用于原始指针、智能指针和迭代器。因此,这些概念通常用作处理迭代器和算法的概念的基本约束。
请注意,这些概念并不要求支持->
运算符。这些概念在头文件<iterator>
中声明。
# 间接支持的基本概念
std::indirectly_writable<P, Val>
- 保证
P
是一个类似指针的对象,支持使用*
运算符来赋值Val
。 - 非const原始指针、智能指针以及迭代器(前提是
Val
可以赋值给P
所指向的位置)满足这个概念。
- 保证
std::indirectly_readable<P>
- 保证
P
是一个类似指针的对象,支持使用*
运算符进行读取访问。 - 原始指针、智能指针和迭代器满足这个概念。
- 要求:
- 对于const和non-const对象,结果值必须具有相同的引用类型(这排除了
std::optional<>
)。这确保了P
的常量性不会传播到它所指向的对象(当运算符返回对成员的引用时,通常不是这种情况)。 std::iter_value_t<P>
必须有效。该类型不必支持->
运算符。
- 对于const和non-const对象,结果值必须具有相同的引用类型(这排除了
- 保证
# 针对间接可读对象的概念
对于间接可读的类似指针概念,你可以检查其他约束:
std::indirectly_movable<InP, OutP>
- 保证
InP
的值可以直接移动赋值给OutP
的值。 - 有了这个概念,以下代码是有效的:
- 保证
void foo(InP inPos, OutP outPos) {
*outPos = std::move(*inPos);
}
2
3
- **要求**:
- `std::indirectly_readable<InP>`满足。
- `InP`值的右值引用对于(`OutP`的值)满足`std::indirectly_writable`。
std::indirectly_movable_storable<InP, OutP>
- 保证即使使用
OutP
所指向类型的(临时)对象,InP
的值也可以间接移动赋值给OutP
的值。 - 有了这个概念,以下代码是有效的:
- 保证即使使用
void foo(InP inPos, OutP outPos) {
OutP::value_type tmp = std::move(*inPos);
*outPos = std::move(tmp);
}
2
3
4
- **要求**:
- `std::indirectly_movable<InP, OutP>`满足。
- `InP`值对于`OutP`所指向的对象满足`std::indirectly_writable`。
- `InP`所指向的值满足`std::movable`。
- `InP`所指向的右值是可复制/移动构造和可赋值的。
std::indirectly_copyable<InP, OutP>
- 保证
InP
的值可以直接赋值给OutP
的值。 - 有了这个概念,以下代码是有效的:
- 保证
void foo(InP inPos, OutP outPos) {
*outPos = *inPos;
}
2
3
- **要求**:
- `std::indirectly_readable<InP>`满足。
- `InP`值的引用对于(`OutP`的值)满足`std::indirectly_writable`。
std::indirectly_copyable_storable<InP, OutP>
- 保证即使使用
OutP
所指向类型的(临时)对象,InP
的值也可以间接赋值给OutP
的值。 - 有了这个概念,以下代码是有效的:
- 保证即使使用
void foo(InP inPos, OutP outPos) {
OutP::value_type tmp = *inPos;
*outPos = tmp;
}
2
3
4
- **要求**:
- `std::indirectly_copyable<InP, OutP>`满足。
- `InP`值的const左值和右值引用对于`OutP`所指向的对象满足`std::indirectly_writable`。
- `InP`所指向的值满足`std::copyable`。
- `InP`所指向的值是可复制/移动构造和可赋值的。
std::indirectly_swappable<P>
std::indirectly_swappable<P1, P2>
- 保证
P
或P1
和P2
的值可以交换(使用std::ranges::iter_swap()
)。 - 要求:
std::indirectly_readable<P1>
(以及std::indirectly_readable<P2>
)满足。- 对于任意两个类型为
P1
和P2
的对象,支持std::ranges::iter_swap()
。
- 保证
std::indirectly_comparable<P1, P2, Comp>
std::indirectly_comparable<P1, P2, Comp, Proj1>
std::indirectly_comparable<P1, P2, Comp, Proj1, Proj2>
- 保证可以比较
P1
和P2
所指向的元素(可选地使用Proj1
和Proj2
进行转换)。 - 要求:
Comp
满足std::indirect_binary_predicate
。std::projected<P1, Proj1>
和std::projected<P2, Proj2>
满足,默认投影为std::identity
。
- 保证可以比较
# 5.3.3 迭代器相关概念
本节列出了用于要求不同类型迭代器的概念。自C++20起,我们有了新的迭代器分类,这些概念就涉及到了这一点,并且它们与范围相关概念相对应。这些概念在头文件<iterator>
中提供。
# std::input_output_iterator<Pos>
- 保证
Pos
支持所有迭代器的基本接口:operator++
和operator*
,其中operator*
必须引用一个值。该概念并不要求迭代器是可复制的(因此,这比算法使用的迭代器的基本要求更低)。 - 要求:
- 满足
std::weakly_incrementable<pos>
。 operator *
返回一个引用。
- 满足
# std::output_iterator<Pos , T>
- 保证
Pos
是一个输出迭代器(可以向其指向的元素赋值的迭代器),可以将类型为T
的值赋给它所指向的值。 - 类型为
Pos
的迭代器可用于通过*i++ = val;
来为类型为T
的值val
赋值。 - 这些迭代器仅适用于单遍迭代。
- 要求:
- 满足
std::input_or_output_iterator<Pos>
。 - 满足
std::indirectly_writable<Pos, I>
。
- 满足
# std::input_iterator<Pos>
- 保证
Pos
是一个输入迭代器(可以从元素中读取值的迭代器)。 - 如果不满足
std::forward_iterator
,这些迭代器仅适用于单遍迭代。 - 要求:
- 满足
std::input_or_output_iterator<Pos>
。 - 满足
std::indirectly_readable<Pos>
。 Pos
的迭代器类别派生自std::input_iterator_tag
。
- 满足
# std::forward_iterator<Pos>
- 保证
Pos
是一个前向迭代器(可以对元素进行多次正向迭代的读取迭代器)。 - 注意,迭代器的
iterator_category
成员可能不匹配。对于产生纯右值(prvalue)的迭代器,它是std::input_iterator_tag
(如果可用)。 - 要求:
- 满足
std::input_iterator<Pos>
。 - 满足
std::incrementable<Pos>
。 Pos
的迭代器类别派生自std::forward_iterator_tag
。
- 满足
# std::bidirectional_iterator<Pos>
- 保证
Pos
是一个双向迭代器(可以对元素进行多次正向和反向迭代的读取迭代器)。 - 注意,迭代器的
iterator_category
成员可能不匹配。对于产生纯右值的迭代器,它是std::input_iterator_tag
(如果可用)。 - 要求:
- 满足
std::forward_iterator<Pos>
。 - 支持使用
operator --
进行反向迭代。 Pos
的迭代器类别派生自std::bidirectional_iterator_tag
。
- 满足
# std::random_access_iterator<Pos>
- 保证
Pos
是一个随机访问迭代器(可以在元素间前后跳跃的读取迭代器)。 - 注意,迭代器的
iterator_category
成员可能不匹配。对于产生纯右值的迭代器,它是std::input_iterator_tag
(如果可用)。 - 要求:
- 满足
std::bidirectional_iterator<Pos>
。 - 满足
std::totally_ordered<Pos>
。 - 满足
std::sized_sentinel_for<Pos, Pos>
。 - 支持
+
、+=
、-
、-=
、[]
操作。 Pos
的迭代器类别派生自std::random_access_iterator_tag
。
- 满足
# std::contiguous_iterator<Pos>
- 保证
Pos
是一个迭代器,用于迭代连续内存中的元素。 - 注意,迭代器的
iterator_category
成员不匹配。如果定义了该类别成员,它只是std::random_access_iterator_tag
,如果值是纯右值,甚至只是std::input_iterator_tag
。 - 要求:
- 满足
std::random_access_iterator<Pos>
。 Pos
的迭代器类别派生自std::contiguous_iterator_tag
。- 对一个元素调用
to_address()
会返回指向该元素的原始指针。
- 满足
# std::sentinel_for<S , Pos>
- 保证
S
可以用作Pos
的哨兵(可能是不同类型的结束迭代器)。 - 要求:
- 满足
std::semiregular<S>
。 - 满足
std::input_or_output_iterator<Pos>
。 - 可以使用
==
和!=
操作符比较Pos
和S
。
- 满足
# std::sized_sentinel_for<S , Pos>
- 保证
S
可以用作Pos
的哨兵(可能是不同类型的结束迭代器),并且可以在常量时间内计算它们之间的距离。 - 若要表明无法在常量时间内计算距离(即使可以计算距离),可以定义
std::disable_sized_sentinel_for<Rg>
产生true
。 - 要求:
- 满足
std::sentinel_for<S, Pos>
。 - 对
Pos
和S
调用operator -
会产生迭代器差值类型的值。 - 没有定义
std::disable_sized_sentinel_for<S , Pos>
产生true
。
- 满足
# 5.3.4 算法相关的迭代器概念
std::permutable<Pos>
- 保证可以使用
operator ++
正向迭代,并通过移动和交换操作重新排列元素。 - 要求:
- 满足
std::forward_iterator<Pos>
。 - 满足
std::indirectly_movable_storable<Pos>
。 - 满足
std::indirectly_swappable<Pos>
。
- 满足
std::mergeable<Pos1 , Pos2 , ToPos>
std::mergeable<Pos1 , Pos2 , ToPos , Comp>
std::mergeable<Pos1 , Pos2 , ToPos , Comp , Proj1>
std::mergeable<Pos1 , Pos2 , ToPos , Comp , Proj1 , Proj2>
- 保证可以将两个已排序序列中
Pos1
和Pos2
指向的元素,通过复制合并到ToPos
指向的序列中。顺序由operator <
或Comp
(应用于可选地通过投影Proj1
和Proj2
转换后的值)定义。 - 要求:
Pos1
和Pos2
都满足std::input_iterator
。- 满足
std::weakly_incrementable<ToPos>
。 Pos1
和Pos2
都满足std::indirectly_copyable<PosN, ToPos>
。- 满足
std::indirect_strict_weak_order
对Comp
以及std::projected<Pos1, Proj1>
和std::projected<Pos2, Proj2>
的要求(默认比较为<
,默认投影为std::identity
),这意味着:Pos1
和Pos2
都满足std::indirectly_readable
。- 满足
std::copy_constructible<Comp>
。 - 对于
Comp&
以及(投影后的)值/引用类型,满足std::strict_weak_order<Comp>
。
std::sortable<Pos>
std::sortable<Pos , Comp>
std::sortable<Pos , Comp , Proj>
- 保证可以使用
operator <
或Comp
(在对值可选地应用投影Proj
之后)对迭代器Pos
指向的元素进行排序。 - 要求:
- 满足
std::permutable<Pos>
。 - 满足
std::indirect_strict_weak_order
对Comp
以及(投影后的)值的要求(默认比较为<
,默认投影为std::identity
),这意味着:- 满足
std::indirectly_readable<Pos>
。 - 满足
std::copy_constructible<Comp>
。 - 对于
Comp&
以及(投影后的)值/引用类型,满足std::strict_weak_order<Comp>
。
- 满足
- 满足
# 5.4 可调用对象相关概念
本节列出了所有与可调用对象相关的概念,包括:
- 函数
- 函数对象
- lambda表达式
- 成员指针(将对象的类型作为额外参数)
# 5.4.1 可调用对象的基本概念
std::invocable<Op , ArgTypes...>
- 保证可以使用类型为
ArgTypes...
的参数调用Op
。 Op
可以是函数、函数对象、lambda表达式或成员指针。- 若要表明操作和传递的参数都不会被修改,可以使用
std::regular_invocable
。不过,请注意这两个概念之间只有语义上的差异,无法在编译时进行检查。因此,概念上的差异仅用于说明意图。 - 例如:
struct S {
int member;
int mfunc(int);
void operator()(int i) const ;
};
void testCallable() {
std::invocable<decltype(testCallable)> // 满足
std::invocable<decltype([](int){}), char> // 满足(char可转换为int)
std::invocable<decltype(&S::mfunc), S, int> // 满足(成员指针、对象、参数)
std::invocable<decltype(&S::member), S>; // 满足(成员指针和对象)
std::invocable<S, int>; // 由于operator()而满足
}
2
3
4
5
6
7
8
9
10
11
12
13
注意,作为类型约束,只需要指定参数类型:
void callWithIntAndString(std::invocable<int, std::string> auto op);
有关完整示例,请参阅将lambda表达式用作非类型模板参数的用法。
- 要求:
- 对于类型为
Op
的op
和类型为ArgTypes...
的args
,std::invoke(op, args)
有效。
- 对于类型为
std::regular_invocable<Op , ArgTypes...>
- 保证可以使用类型为
ArgTypes...
的参数调用Op
,并且调用不会改变传递的操作状态和传递的参数状态。 Op
可以是函数、函数对象、lambda表达式或成员指针。- 注意,与
std::invocable
概念的差异纯粹是语义上的,无法在编译时进行检查。因此,概念上的差异仅用于说明意图。 - 要求:
- 对于类型为
Op
的op
和类型为ArgTypes...
的args
,std::invoke(op, args)
有效。
- 对于类型为
std::predicate<Op , ArgTypes...>
- 保证可调用对象(函数、函数对象、lambda表达式)
Op
是一个谓词,可以使用类型为ArgTypes...
的参数进行调用。 - 这意味着调用不会改变传递的操作状态和传递的参数状态。
- 要求:
- 满足
std::regular_invocable<Op>
。 - 所有使用
ArgTypes...
调用Op
的操作都产生一个可以用作布尔值的值。
- 满足
std::relation<Pred , T1 , T2>
- 保证任何两个类型为
T1
和T2
的对象之间存在二元关系,即它们可以作为参数传递给二元谓词Pred
。 - 这意味着调用不会改变传递的操作状态和传递的参数状态。
- 注意,与
std::equivalence_relation
和std::strict_weak_order
概念的差异纯粹是语义上的,无法在编译时进行检查。因此,概念上的差异仅用于说明意图。 - 要求:
- 对于
pred
以及任何T1
和T2
类型的对象组合,满足std::predicate
。
- 对于
std::equivalence_relation<Pred , T1 , T2>
- 保证当使用
Pred
进行比较时,任何两个类型为T1
和T2
的对象之间存在等价关系。也就是说,它们可以作为参数传递给二元谓词Pred
,并且该关系是自反的、对称的和传递的。 - 这意味着调用不会改变传递的操作状态和传递的参数状态。
- 注意,与
std::relation
和std::strict_weak_order
概念的差异纯粹是语义上的,无法在编译时进行检查。因此,概念上的差异仅用于说明意图。 - 要求:
- 对于
pred
以及任何T1
和T2
类型的对象组合,满足std::predicate
。
- 对于
std::strict_weak_order<Pred , T1 , T2>
- 保证当使用
Pred
进行比较时,任何两个类型为T1
和T2
的对象之间存在严格弱序关系。也就是说,它们可以作为参数传递给二元谓词Pred
,并且该关系是反自反的和传递的。 - 这意味着调用不会改变传递的操作状态和传递的参数状态。
- 注意,与
std::relation
和std::equivalence_relation
概念的差异纯粹是语义上的,无法在编译时进行检查。因此,概念上的差异仅用于说明意图。 - 要求:
- 对于
pred
以及任何T1
和T2
类型的对象组合,满足std::predicate
。
- 对于
std::uniform_random_bit_generator<Op>
- 保证
Op
可以用作随机数生成器,理想情况下,它返回的无符号整数值具有相等的概率。 - 这个概念在头文件
<random>
中定义。 - 要求:
- 满足
std::invocable<Op&>
。 - 调用的结果满足
std::unsigned_integral
。 - 支持表达式
Op::min()
和Op::max()
,并且它们产生与生成器调用相同的类型。 Op::min()
小于Op::max()
。
- 满足
# 5.4.2 迭代器使用的可调用对象概念
std::indirectly_unary_invocable<Op, Pos>
- 保证
Op
可以使用Pos
所引用的值进行调用。 - 注意,与
indirectly_regular_unary_invocable
概念的区别纯粹是语义上的,无法在编译时进行检查。因此,这些不同的概念仅用于记录意图。 - 要求:
- 满足
std::indirectly_readable<Pos>
。 - 满足
std::copy_constructible<Op>
。 Op&
以及Pos
的值类型和(公共)引用类型满足std::invocable
。- 使用值和引用调用
Op
的结果具有公共引用类型。
- 满足
std::indirectly_regular_unary_invocable<Op, Pos>
- 保证
Op
可以使用Pos
所引用的值进行调用,并且调用不会改变Op
的状态。 - 注意,与
std::indirectly_unary_invocable
概念的区别纯粹是语义上的,无法在编译时进行检查。因此,这些不同的概念仅用于记录意图。 - 要求:
- 满足
std::indirectly_readable<Pos>
。 - 满足
std::copy_constructible<Op>
。 Op&
以及Pos
的值类型和(公共)引用类型满足std::regular_invocable
。- 使用值和引用调用
Op
的结果具有公共引用类型。
- 满足
std::indirect_unary_predicate<Pred, Pos>
- 保证一元谓词
Pred
可以使用Pos
所引用的值进行调用。 - 要求:
- 满足
std::indirectly_readable<Pos>
。 - 满足
std::copy_constructible<Pred>
。 Pred&
以及Pos
的值类型和(公共)引用类型满足std::predicate
。- 所有对
Pred
的调用都产生一个可以用作布尔值的值。
- 满足
std::indirect_binary_predicate<Pred, Pos1, Pos2>
- 保证二元谓词
Pred
可以使用Pos1
和Pos2
所引用的值进行调用。 - 要求:
Pos1
和Pos2
都满足std::indirectly_readable
。- 满足
std::copy_constructible<Pred>
。 Pred&
、Pos1
的值类型或(公共)引用类型以及Pos2
的值类型或(公共)引用类型满足std::predicate
。- 所有对
Pred
的调用都产生一个可以用作布尔值的值。
std::indirect_equivalence_relation<Pred, Pos1>、std::indirect_equivalence_relation<Pred, Pos1, Pos2>
- 保证二元谓词
Pred
可以被调用,以检查Pos1
中的两个值或Pos1
/Pos2
中的两个值是否等价。 - 注意,与
std::indirectly_strict_weak_order
概念的区别纯粹是语义上的,无法在编译时进行检查。因此,这些不同的概念仅用于记录意图。 - 要求:
Pos1
和Pos2
都满足std::indirectly_readable
。- 满足
std::copy_constructible<Pred>
。 Pred&
、Pos1
的值类型或(公共)引用类型以及Pos2
的值类型或(公共)引用类型满足std::equivalence_relation
。- 所有对
Pred
的调用都产生一个可以用作布尔值的值。
std::indirect_strict_weak_order<Pred, Pos1>、std::indirect_strict_weak_order<Pred, Pos1, Pos2>
- 保证二元谓词
Pred
可以被调用,以检查Pos1
中的两个值或Pos1
/Pos2
中的两个值是否具有严格弱序关系。 - 注意,与
std::indirectly_equivalence_relation
概念的区别纯粹是语义上的,无法在编译时进行检查。因此,这些不同的概念仅用于记录意图。 - 要求:
Pos1
和Pos2
都满足std::indirectly_readable
。- 满足
std::copy_constructible<Pred>
。 Pred&
、Pos1
的值类型或(公共)引用类型以及Pos2
的值类型或(公共)引用类型满足std::strict_weak_order
。- 所有对
Pred
的调用都产生一个可以用作布尔值的值。
# 5.5 辅助概念
本节描述了一些标准化的概念,这些概念主要用于实现其他概念。在应用代码中,你通常不应使用它们。
# 5.5.1 特定类型属性的概念
# std::default_initializable<T>
- 保证类型
T
支持默认构造(声明/构造时无需初始值)。 - 要求:
- 满足
std::constructible_from<T>
。 - 满足
std::destructible<T>
。 T{}
有效。T x;
有效。
- 满足
# std::move_constructible<T>
- 保证
T
类型的对象可以用其类型的右值进行初始化。 - 这意味着以下操作是有效的(尽管可能是复制而非移动):
T t2{std::move(t1)}
(对于任何T
类型的t1
)。之后,t2
应具有t1
之前的值,这是一个无法在编译时检查的语义约束。 - 要求:
- 满足
std::constructible_from<T, T>
。 - 满足
std::convertible<T, T>
。 - 满足
std::destructible<T>
。
- 满足
# std::copy_constructible<T>
- 保证
T
类型的对象可以用其类型的左值进行初始化。 - 这意味着以下操作是有效的:
T t2{t1}
(对于任何T
类型的t1
)。之后,t2
应等于t1
,这是一个无法在编译时检查的语义约束。 - 要求:
- 满足
std::move_constructible<T>
。 std::constructible_from
和std::convertible_to
对于任何T
、T&
、const T
和const T&
到T
的转换都满足。- 满足
std::destructible<T>
。
- 满足
# std::destructible<T>
- 保证
T
类型的对象在被销毁时不会抛出异常。 注意,即使是已实现的析构函数,除非你显式用noexcept(false)
标记,否则也会自动保证不抛出异常。 - 要求:
- 类型特性
std::is_nothrow_destructible_v<T>
的值为true
。
- 类型特性
# std::swappable<T>
- 保证可以交换两个
T
类型对象的值。 - 要求:
- 可以对两个
T
类型的对象调用std::ranges::swap()
。
- 可以对两个
# 5.5.2 可递增类型的概念
# std::weakly_incrementable<T>
- 保证类型
T
支持递增运算符。 - 注意,这个概念并不要求对同一值进行两次递增的结果相等。因此,这仅适用于单遍迭代的要求。
- 与
std::incrementable
相比,以下情况该概念也满足:- 类型不可默认构造、不可复制或不可比较相等。
- 后缀递增运算符返回
void
(或任何其他类型)。 - 对同一值进行两次递增得到不同的结果。
- 注意,与
std::incrementable
概念的差异纯粹是语义上的,因此递增结果不同的类型在技术上仍可能满足incrementable
概念。为了针对这种语义差异实现不同的行为,你应该使用迭代器概念。 - 要求:
- 满足
std::default_initializable<T>
。 - 满足
std::movable<T>
。 std::iter_difference_t<T>
是一个有效的有符号整数类型。
- 满足
# std::incrementable<T>
- 保证类型
T
是一个可递增类型,这样你就可以对相同的值序列进行多次迭代。 - 与
std::weakly_incrementable
相比,该概念要求:- 对同一值进行两次递增的结果相同(就像前向迭代器那样)。
- 类型
T
可默认构造、可复制且可比较相等。 - 后缀递增运算符返回迭代器的副本(返回类型为
T
)。
- 注意,与
std::weakly_incrementable
概念的差异纯粹是语义上的,因此递增结果不同的类型在技术上仍可能满足incrementable
概念。为了针对这种语义差异实现不同的行为,你应该使用迭代器概念。 - 要求:
- 满足
std::weakly_incrementable<T>
。 - 满足
std::regular<T>
,这样类型就可默认构造、可复制且可比较相等。
- 满足