第7章 范围和视图的实用工具
# 第7章 范围和视图的实用工具
上一章介绍了范围和视图,本章将首次概述C++20引入的用于处理范围和视图的重要细节及实用工具(函数、类型和对象)。具体而言,本章涵盖:
- 用于创建视图的最重要的通用实用工具。
- 对迭代器进行分类的新方式及其影响。
- 新的迭代器和哨兵类型。
- 用于处理范围和迭代器的新函数及类型函数。
最后,本章还列出了C++20现在提供的所有算法,并详细说明了这些算法是否可用于处理整个范围,以及一些相关的关键要点。
请注意,本书其他部分也对范围的特性进行了详细描述:
- 关于概念的一般章节介绍了所有范围概念。
- 下一章介绍了标准视图类型的所有细节。
# 7.1 将范围用作视图的关键实用工具
正如前面介绍视图及其使用方式时所述,C++提供了多个范围适配器(range adaptors)和范围工厂(range factories),以便你能轻松创建性能最佳的视图。
其中一些适配器适用于特定的视图类型。不过,有些适配器可能会根据传递范围的特性创建不同的结果。例如,如果传递的范围已经具备结果所需的特性,适配器可能只是返回该范围。
有几个关键的范围适配器和工厂,可方便地创建视图,或将范围转换为具有特定特性(与内容无关)的视图:
std::views::all()
是将传递的范围转换为视图的主要范围适配器。std::views::counted()
是将传递的起始位置和计数/大小转换为视图的主要范围工厂。std::views::common()
是一个范围适配器,它将(起始)迭代器和哨兵(结束迭代器)类型不同的范围,转换为起始和结束迭代器类型一致的视图。
本节将对这些适配器进行介绍。
# 7.1.1 std::views::all()
范围适配器std::views::all()
用于将任何尚未成为视图的范围转换为视图。这样,你就能确保以一种高效的方式处理范围中的元素。
all(rg)
返回以下结果:
- 如果
rg
已经是视图,则返回rg
的副本。 - 否则,如果
rg
是左值(有名称的范围对象),则返回std::ranges::ref_view
类型的rg
。 - 否则,如果
rg
是右值(未命名的临时范围对象或用std::move()
标记的范围对象),则返回std::ranges::owning_view
类型的rg
。
例如:
std::vector<int> getColl();//function returning atmp. container
std::vector coll{1, 2, 3};// a container
std::ranges::iota_view aView{1};// a view
auto v1 = std::views::all(aView);
auto v2 = std::views::all(coll);
auto v3 = std::views::all(std::views::all(coll));
auto v4 = std::views::all(getColl());
auto v5 = std::views::all(std::move(coll));
2
3
4
5
6
7
8
all()
适配器通常用于将范围作为轻量级对象传递。将范围转换为视图有两个好处:
- 性能方面:当范围被复制时(例如,因为参数按值传递参数),使用视图要便宜得多。这是因为移动或(如果支持)复制视图的操作开销被保证是很低的。因此,将范围作为视图传递可以降低调用成本。
例如:
void foo(std::ranges::input_range auto coll) // NOTE: takes range by value
{
for (const auto& elem : coll) {
...
}
}
std::vector<std::string> coll{ ... };
foo(coll);
foo(std::views::all(coll));
2
3
4
5
6
7
8
9
10
11
在此处使用all()
有点类似于使用引用包装器(用std::ref()
或std::cref()
创建)。不过,all()
的优势在于传递的对象仍然支持范围的常用接口。
将容器传递给协程(协程通常必须按值接受参数)可能是这种技术的另一个应用场景。
- 满足视图要求方面:使用
all()
的另一个原因是满足需要视图的约束条件。提出这种要求的一个原因可能是确保传递参数的成本不高(这就带来了上述好处)。
例如:
void foo(std::ranges::view auto coll) // NOTE: takes view by value
{
for (const auto& elem : coll) {
...
}
}
std::vector<std::string> coll{ ... };
foo(coll);
foo(std::views::all(coll));
2
3
4
5
6
7
8
9
10
11
使用all()
可能会隐式发生。用all()
进行隐式转换的一个例子是用容器初始化视图类型。
# 类型std::views::all_t<>
C++20还将类型std::views::all_t<>
定义为all()
返回的类型。它遵循完美转发规则,这意味着值类型和右值引用类型都可用于指定右值的类型:
std::vector<int> v{0, 8, 15, 47, 11, -1, 13};
std::views::all_t<decltype(v)> a1{v};// ERROR
std::views::all_t<decltype(v)&> a2{v};// ref_view<vector<int>>
std::views::all_t<decltype(v)&&> a3{v};// ERROR
std::views::all_t<decltype(v)> a4{std::move(v)};// owning_view<vector<int>>
std::views::all_t<decltype(v)&> a5{std::move(v)};// ERROR
std::views::all_t<decltype(v)&&> a6{std::move(v)};// owning_view<vector<int>>
2
3
4
5
6
7
8
接受范围的视图通常使用类型std::views::all_t<>
来确保传递的范围确实是一个视图。因此,如果你传递的范围还不是视图,就会隐式创建一个视图。例如,调用:
std::views::take(coll, 3)
与调用:
std::ranges::take_view{std::ranges::ref_view{coll}, 3};
效果相同。
其工作原理如下(以take
视图为例):
- 视图类型要求传递视图:
namespace std::ranges {
template<view V>
class take_view : public view_interface<take_view<V>> {
public :
constexpr take_view(V base, range_difference_t<V> count);
...
};
}
2
3
4
5
6
7
8
- 推导指引要求视图的元素类型为
std::views::all_t
类型:
namespace std::ranges {
// 强制转换为视图的推导指引:
template<range R>
take_view(R&&, range_difference_t<R>) -> take_view<views::all_t<R>>;
}
2
3
4
5
- 现在,当传递一个容器时,视图的范围类型必须是
std::views::all_t<>
类型。因此,容器会隐式转换为ref_view
(如果它是左值)或owning_view
(如果它是右值)。效果就好像我们调用了以下代码:
std::ranges::ref_view rv{coll};// convert to aref_view<>
std::ranges::take_view tv(rv, 3);// and use view and count to initialize the take_view<>
2
概念viewable_range
可用于检查一个类型是否可用于all_t<>
(因此相应的对象可传递给all()
):
std::ranges::viewable_range<std::vector<int>> // true
std::ranges::viewable_range<std::vector<int>&> // true
std::ranges::viewable_range<std::vector<int>&&> // true
std::ranges::viewable_range<std::ranges::iota_view<int>> // true
std::ranges::viewable_range<std::queue<int>> //false
2
3
4
5
# 7.1.2 std::views::counted()
范围工厂函数std::views::counted()
提供了一种最灵活的方式,通过起始迭代器和元素数量来创建视图。通过调用std::views::counted(beg, sz)
,它会创建一个视图,该视图包含从beg
开始的范围内的前sz
个元素。
对于仅支持移动的视图类型的左值,存在一个问题,即尽管满足viewable_range
概念,但all()
的使用是不合法的。
使用视图时,确保起始迭代器和元素数量有效是程序员的责任。否则,程序会出现未定义行为。
元素数量可以为0,这意味着该范围为空。
元素数量存储在视图中,因此是稳定的。即使在视图所引用的范围中插入了新元素,元素数量也不会改变。例如:
std::list lst{1, 2, 3, 4, 5, 6, 7, 8};
auto c = std::views::counted(lst.begin(), 5);
print(c); // 1 2 3 4 5
lst.insert(++lst.begin(), 0); // 在lst中插入新的第二个元素
print(c); // 1 0 2 3 4
2
3
4
5
请注意,如果你想要获取一个范围的前num
个元素的视图,take
视图提供了一种更方便、更安全的方式:
std::list lst{1, 2, 3, 4, 5, 6, 7, 8};
auto v1 = std::views::take(lst, 3); // 包含前三个元素的视图(如果存在)
2
take
视图会检查是否有足够的元素,如果没有,则返回较少数量的元素。
通过结合使用drop
视图,你甚至可以跳过特定数量的起始元素:
std::list lst{1, 2, 3, 4, 5, 6, 7, 8};
auto v2 = std::views::drop(lst, 2) | std::views::take(3); // 第3到第5个元素(如果存在)
2
# counted()的类型
counted(beg, sz)
根据被调用范围的特性会产生不同的类型:
- 如果传递的起始迭代器是
contiguous_iterator
(指向存储在连续内存中的元素),它会产生一个std::span
。这适用于原始指针、原始数组以及std::vector<>
或std::array<>
的迭代器。 - 否则,如果传递的起始迭代器是
random_access_iterator
(支持在元素间前后跳跃),它会产生一个std::ranges::subrange
。这适用于std::deque<>
的迭代器。 - 否则,它会产生一个
std::ranges::subrange
,其中起始迭代器是std::counted_iterator
,结束哨兵是std::default_sentinel_t
类型的哑元哨兵。这意味着在子范围中的迭代器在迭代时会进行计数。这适用于列表、关联容器和无序容器(哈希表)的迭代器。
例如:
std::vector<int> vec{1, 2, 3, 4, 5, 6, 7, 8, 9};
auto vec5 = std::ranges::find(vec, 5);
auto v1 = std::views::counted(vec5, 3); // 包含vec中元素5及之后两个元素的视图
// v1是std::span<int>
std::deque<int> deq{1, 2, 3, 4, 5, 6, 7, 8, 9};
auto deq5 = std::ranges::find(deq, 5);
auto v2 = std::views::counted(deq5, 3); // 包含deq中元素5及之后两个元素的视图
// v2是std::ranges::subrange<std::deque<int>::iterator>
std::list<int> lst{1, 2, 3, 4, 5, 6, 7, 8, 9};
auto lst5 = std::ranges::find(lst, 5);
auto v3 = std::views::counted(lst5, 3); // 包含lst中元素5及之后两个元素的视图
// v3是std::ranges::subrange<std::counted_iterator<std::list<int>::iterator>,
// std::default_sentinel_t>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
请注意,这段代码存在风险,如果集合中不存在5以及它后面的两个元素,就会产生未定义行为。因此,如果你不确定是否存在这种情况,就应该进行检查。
# 7.1.3 std::views::common()
范围适配器std::views::common()
会为传递范围的起始迭代器和哨兵(结束迭代器)生成具有统一类型的视图。它的行为类似于范围适配器std::views::all()
,不同之处在于,如果传递范围的迭代器类型不同,它会从传递的参数创建一个std::ranges::common_view
。
例如,假设我们想要调用一个传统算法algo()
,该算法要求起始迭代器和结束迭代器类型相同。那么我们可以像这样对可能不同类型的迭代器调用该算法:
template<typename BegT, typename EndT>
void callAlgo(BegT beg, EndT end)
{
auto v = std::views::common(std::ranges::subrange(beg,end));
algo(v.begin(), v.end()); // 假设algo()要求迭代器类型相同
}
2
3
4
5
6
common(rg)
会产生:
- 如果
rg
已经是起始和结束迭代器类型相同的视图,则返回rg
的副本。 - 否则,如果
rg
是起始和结束迭代器类型相同的范围对象,则返回rg
的std::ranges::ref_view
。 - 否则,返回
rg
的std::ranges::common_view
。
例如:
std::list<int> lst;
std::ranges::iota_view iv{1, 10};
...
auto v1 = std::views::common(lst); // std::ranges::ref_view<decltype(lst)>
auto v2 = std::views::common(iv); // decltype(iv)
auto v3 = std::views::common(std::views::all(vec));
// std::ranges::ref_view<decltype(lst)>
std::list<int> lst {1, 2, 3, 4, 5, 6, 7, 8, 9};
auto vt = std::views::take(lst, 5); // begin()和end()类型不同
auto v4 = std::views::common(vt); // std::ranges::common_view<decltype(vt)>
2
3
4
5
6
7
8
9
10
11
请注意,std::ranges::common_view
的构造函数和辅助类型std::common::iterator
都要求传递的迭代器类型不同。因此,如果你不确定迭代器类型是否不同,就应该使用这个适配器。
# 7.2 新的迭代器类别
迭代器具有不同的能力。这些能力很重要,因为一些算法需要特定的迭代器能力。例如,排序算法要求迭代器能够进行随机访问,否则性能会很差。因此,迭代器有不同的类别,C++20中新增了一个类别:连续迭代器(contiguous iterator)。这些类别能力列于表“迭代器类别”中。
迭代器类别 | 能力 | 提供容器 |
---|---|---|
输出 | 向前写入 | 输出流迭代器、插入器 |
输入 | 向前读取一次 | 输入流迭代器 |
前向 | 向前读取 | std::forward_list<> 、无序容器 |
双向 | 向前和向后读取 | list<> 、set<> 、multiset<> 、map<> 、multimap<> |
随机访问 | 随机访问读取 | deque<> |
连续 | 读取存储在连续内存中的元素 | array<> 、vector<> 、string 、C风格数组 |
表7.1 迭代器类别
请注意,C++20中这些类别的重要细节发生了变化。最重要的变化有:
- 新增的迭代器类别
contiguous
。对于这个类别,定义了一个新的迭代器标签类型:std::contiguous_iterator_tag
。 - 产生临时对象(prvalue)的迭代器现在可以具有比输入迭代器更强的类别。前向迭代器不再要求产生引用。
- 输入迭代器不再保证可复制。你应该只对它们进行移动操作。
- 输入迭代器的后置递增运算符不再要求返回任何值。对于输入迭代器
pos
,不应再使用pos++
,而应使用++pos
。
这些变化不向后兼容:
- 对于
std::vector
或数组等容器的迭代器,检查其是否为随机访问迭代器的方法不再适用。相反,你必须检查迭代器是否支持随机访问。 - 认为前向、双向或随机访问迭代器的值是引用的假设不再总是成立。
因此,C++20引入了一个新的可选迭代器属性iterator_concept
,并定义可以设置这个属性来表示与传统类别不同的C++20类别。例如:
std::vector vec{1, 2, 3, 4};
auto pos1 = vec.begin();
decltype(pos1)::iterator_category
decltype(pos1)::iterator_concept
auto v = std::views::iota(1);
auto pos2 = v.begin();
decltype(pos2)::iterator_category
decltype(pos2)::iterator_concept
2
3
4
5
6
7
8
9
// 类型为std::random_access_iterator_tag // 类型为std::contiguous_iterator_tag // 类型为std::input_iterator_tag // 类型为std::random_access_iterator_tag
请注意,std::iterator_traits
没有提供iterator_concept
成员,并且对于迭代器,iterator_category
成员可能并不总是被定义。
迭代器概念和范围概念考虑了C++20的新分类。自C++20起,对于需要处理迭代器类别的代码,这会产生以下影响:
- 使用迭代器概念和范围概念,而不是
std::iterator_traits<I>::iterator_category
来检查类别。 - 如果你实现自己的迭代器类型,请根据 http://wg21.link/p2259 (opens new window) 中的提示,考虑提供
iterator_category
和/或iterator_concept
。
还要注意,有效的C++20输入迭代器可能根本不是C++17迭代器(例如,不提供复制功能)。对于这些迭代器,传统的迭代器特性不起作用。因此,自C++20起:
- 使用
std::iter_value_t
,而不是iterator_traits<>::value_type
。 - 使用
std::iter_reference_t
,而不是iterator_traits<>::reference
。 - 使用
std::iter_difference_t
,而不是iterator_traits<>::difference_type
。
# 7.3 新的迭代器和哨兵类型
为了(更好地)支持范围和视图,C++20引入了几个新的迭代器和哨兵类型:
std::counted_iterator
:用于自身带有计数以指定范围结束位置的迭代器。std::common_iterator
:用于可用于两种不同类型迭代器的通用迭代器类型。std::default_sentinel_t
:用于强制迭代器检查其结束位置的结束迭代器。std::unreachable_sentinel_t
:用于永远无法到达的结束迭代器,意味着该范围是无限的。std::move_sentinel
:用于将复制操作映射为移动操作的结束迭代器。
# 7.3.1 std::counted_iterator
计数迭代器(std::counted_iterator
)是一种带有计数器的迭代器,该计数器用于指定可迭代的最大元素数量。使用这种迭代器有两种方式:
- 在检查剩余元素数量的同时进行迭代:
for (std::counted_iterator pos{coll.begin(), 5}; pos.count() > 0; ++pos) {
std::cout << *pos << '\n';
}
std::cout << '\n';
2
3
4
- 在与默认哨兵(
std::default_sentinel
)进行比较的同时进行迭代:
for (std::counted_iterator pos{coll.begin(), 5}; pos != std::default_sentinel; ++pos) {
std::cout << *pos << '\n';
}
2
3
当视图适配器std::ranges::counted()
为非随机访问迭代器生成子范围时,会用到这个特性。
下表列出了std::counted_iterator<>
类的操作API。
操作 | 效果 |
---|---|
countedItor pos{} countedItor pos{pos2, num} countedItor pos{pos2} | 创建一个不指向任何元素的计数迭代器(计数为0) 创建一个从 pos2 开始、包含num 个元素的计数迭代器 创建一个 pos2 计数迭代器的(类型转换后的)副本 |
pos.count() pos.base() ... pos == std::default_sentinel pos != std::default_sentinel | 返回剩余元素的数量(0表示已到达末尾) 返回(底层迭代器的)副本 底层迭代器类型的所有标准迭代器操作 判断迭代器是否已到达末尾 判断迭代器是否未到达末尾 |
表7.2 std::counted_iterator<>
类的操作
程序员需要确保:
- 初始计数不高于最初传递的迭代器可迭代的元素数量。
- 计数迭代器的递增次数不超过计数次数。
- 计数迭代器不会访问超过计数指定数量的元素(和往常一样,最后一个元素之后的位置是有效的)。
# 7.3.2 std::common_iterator
std::common_iterator<>
类型用于统一两个迭代器的类型。它包装了两个迭代器,使得从外部看,这两个迭代器具有相同的类型。在内部,存储两个类型之一的值(为此,迭代器通常使用std::variant<>
)。
例如,接受起始迭代器和结束迭代器的传统算法要求这些迭代器具有相同的类型。如果你有不同类型的迭代器,可以使用这个类型函数来调用这些算法:
algo(beg, end); // 如果因为类型不同而报错
algo(std::common_iterator<decltype(beg), decltype(end)>{beg}, // 正确
std::common_iterator<decltype(beg), decltype(end)>{end});
2
3
注意,如果传递给common_iterator<>
的类型相同,这将是一个编译时错误。因此,在泛型代码中,你可能需要这样调用:
template<typename BegT, typename EndT>
void callAlgo(BegT beg, EndT end)
{
if constexpr(std::same_as<BegT, EndT>) {
algo(beg, end);
}
else {
algo(std::common_iterator<decltype(beg), decltype(end)>{beg},
std::common_iterator<decltype(beg), decltype(end)>{end});
}
}
2
3
4
5
6
7
8
9
10
11
实现相同效果的更便捷方式是使用common()
范围适配器:
template<typename BegT, typename EndT>
void callAlgo(BegT beg, EndT end) {
auto v = std::views::common(std::ranges::subrange(beg,end));
algo(v.begin(), v.end());
}
2
3
4
5
# 7.3.3 std::default_sentinel
默认哨兵(std::default_sentinel
)是一种不提供任何操作的迭代器。C++20提供了相应的对象std::default_sentinel
,其类型为std::default_sentinel_t
。它在<iterator>
头文件中定义。该类型没有成员:
namespace std {
class default_sentinel_t { };
inline constexpr default_sentinel_t default_sentinel{};
}
2
3
4
这个类型和值作为一个虚拟的哨兵(结束迭代器),用于迭代器能够自行判断结束位置而无需查看结束迭代器的情况。对于这些迭代器,定义了与std::default_sentinel
的比较操作,但实际上从不使用默认哨兵。相反,迭代器会在内部检查自己是否已到达末尾(或距离末尾有多远)。
例如,计数迭代器为自身定义了一个与默认哨兵比较的operator ==
,用于检查是否到达末尾:
namespace std {
template<std::input_or_output_iterator I>
class counted_iterator {
// ...
friend constexpr bool operator==(const counted_iterator& p, std::default_sentinel_t) {
// ... 返回p是否已到达末尾
}
};
}
2
3
4
5
6
7
8
9
这使得以下代码成为可能:
// 遍历前五个元素:
for (std::counted_iterator pos{coll.begin(), 5}; pos != std::default_sentinel; ++pos) {
std::cout << *pos << '\n';
}
2
3
4
标准定义了以下与默认哨兵相关的操作:
- 对于
std::counted_iterator
:- 使用
operator ==
和!=
进行比较。 - 使用
operator -
计算距离。
- 使用
std::views::counted()
可能会创建一个由计数迭代器和默认哨兵组成的子范围。- 对于
std::istream_iterator
:- 默认哨兵可用作初始值,效果与默认构造函数相同。
- 使用
operator ==
和!=
进行比较。
- 对于
std::istreambuf_iterator
:- 默认哨兵可用作初始值,效果与默认构造函数相同。
- 使用
operator ==
和!=
进行比较。
- 对于
std::ranges::basic_istream_view<>
:- 输入流视图(
istream view
)的end()
返回std::default_sentinel
。 - 输入流视图迭代器可以使用
operator ==
和!=
进行比较。
- 输入流视图(
- 对于
std::ranges::take_view<>
:take
视图的end()
可能返回std::default_sentinel
。
- 对于
std::ranges::split_view<>
:split
视图的end()
可能返回std::default_sentinel
。split
视图迭代器可以使用operator ==
和!=
进行比较。
# 7.3.4 std::unreachable_sentinel
C++20引入了类型为std::unreachable_sentinel_t
的值std::unreachable_sentinel
,用于指定一个无法到达的哨兵(范围的结束迭代器)。它实际上表示“根本不要和我比较” 。这个类型和值可用于指定无限制的范围。
使用它时,可以优化生成的代码,因为编译器可以检测到将它与另一个迭代器进行比较永远不会得到true
,这意味着它可能会跳过所有与结束位置的比较检查。
例如,如果我们知道集合中存在值42,可以这样查找它:
auto pos42 = std::ranges::find(coll.begin(), std::unreachable_sentinel, 42);
通常情况下,算法会同时与42和coll.end()
(或作为结束/哨兵传递的任何值)进行比较。由于使用了unreachable_sentinel
,它与任何迭代器的比较总是返回false
,编译器可以优化代码,只与42进行比较。当然,程序员必须确保集合中存在42。
iota
视图提供了一个在无限范围中使用unreachable_sentinel
的示例。
# 7.3.5 std::move_sentinel
自C++11起,C++标准库就有一个迭代器类型std::move_iterator
,它可以将迭代器的行为从复制值映射为移动值。C++20引入了相应的哨兵类型。
这个类型只能用于使用operator ==
和!=
将移动哨兵与移动迭代器进行比较,以及计算移动迭代器和移动哨兵之间的差值(如果支持的话)。
如果你有一个迭代器和一个哨兵构成了一个有效的范围(满足std::sentinel_for
概念),你可以将它们转换为移动迭代器和移动哨兵,以得到一个仍然满足std::sentinel_for
概念的有效范围。
你可以如下使用移动哨兵:
std::list<std::string> coll{ "tic ", "tac ", "toe "};
std::vector<std::string> v1;
std::vector<std::string> v2;
// 将字符串复制到v1中:
for (auto pos{coll.begin()}; pos != coll.end(); ++pos) {
v1.push_back(*pos);
}
// 将字符串移动到v2中:
for (std::move_iterator pos{coll.begin()};
pos != std::move_sentinel(coll.end()); ++pos) {
v2.push_back(*pos);
}
2
3
4
5
6
7
8
9
10
11
12
注意,这会导致迭代器和哨兵具有不同的类型。因此,你不能直接初始化vector
,因为它要求起始和结束的类型相同。在这种情况下,你还需要对结束位置使用移动迭代器:
std::vector<std::string> v3{std::move_iterator{coll.begin()},
std::move_sentinel{coll.end()}}; // 错误
std::vector<std::string> v4{std::move_iterator{coll.begin()},
std::move_iterator{coll.end()}}; // 正确
2
3
4
5
# 7.4 处理范围的新函数
范围库在std::ranges
和std
命名空间中提供了几个通用的辅助函数。
其中有一些在C++20之前就已存在,在std
命名空间中名称相同或略有不同(为了向后兼容,仍然提供这些函数)。然而,范围实用函数通常对指定的功能提供更好的支持。它们可能修复了旧版本存在的缺陷,或者使用概念来约束其使用。
这些 “函数” 要求是函数对象、仿函数,甚至是定制点对象(CPO,Customization Point Object,是要求为半正则的函数对象,并保证所有实例相等)。
这意味着它们不支持实参依赖查找(ADL,Argument-Dependent Lookup)。
因此,你应该优先使用std::ranges
中的实用函数,而不是std
中的实用函数。
# 7.4.1 处理范围(和数组)元素的函数
下表列出了处理范围及其元素的新的独立函数。它们也适用于原生数组。
几乎所有相应的实用函数在C++20之前就已直接存在于std
命名空间中。唯一的例外是:
ssize()
,它在C++20中作为std::ssize()
引入。cdata()
,在std
命名空间中从未存在过。
你可能想知道为什么在std::ranges
命名空间中引入新函数,而不是修复std
命名空间中现有的函数。原因是如果我们修复现有函数,现有的代码可能会被破坏,而向后兼容是C++的一个重要目标。
那么下一个问题是何时使用哪个函数。这里的指导原则很简单:优先使用std::ranges
命名空间中的函数/实用函数,而不是std
命名空间中的函数。
原因不仅在于std::ranges
命名空间中的函数/实用函数使用了概念,这有助于在编译时发现问题和错误;另一个优先使用std::ranges
命名空间中新函数的原因是,std
命名空间中的函数有时存在缺陷,而std::ranges
中的新实现修复了这些缺陷:
- 一个问题是实参依赖查找(ADL)。
- 另一个问题是常量正确性。
| 函数 | 含义 |
| ------------------------------------------------------------ | ------------------------------------------------------------ |
|
std::ranges::empty(rg )
std::ranges::size(rg )
std::ranges::ssize(rg )
std::ranges::begin(rg )
std::ranges::end(rg )
std::ranges::cbegin(rg )
std::ranges::cend(rg )
std::ranges::rbegin(rg )
std::ranges::rend(rg )
std::ranges::crbegin(rg )
std::ranges::crend(rg )
std::ranges::data(rg )
std::ranges::cdata(rg )
| 判断范围是否为空
返回范围的大小
以有符号类型的值返回范围的大小
返回指向范围第一个元素的迭代器
返回范围的哨兵(指向末尾的迭代器)
返回指向范围第一个元素的常量迭代器
返回范围的常量哨兵(指向末尾的常量迭代器)
返回指向范围第一个元素的反向迭代器
返回范围的反向哨兵(指向末尾的迭代器)
返回指向范围第一个元素的反向常量迭代器
返回范围的反向常量哨兵(指向末尾的常量迭代器)
返回范围的原始数据
返回包含常量元素的范围的原始数据 |
表7.3 处理范围元素的通用函数
# std::ranges::begin()
如何解决ADL问题
让我们看看为什么在泛型代码中使用std::ranges::begin()
比使用std::begin()
处理范围更好。问题在于,如果不以某种巧妙的方式使用,实参依赖查找对于std::begin()
并不总是有效。
假设你想编写泛型代码,调用为任意类型的范围对象obj
定义的begin()
函数。我们在使用std::begin()
时遇到的问题如下:
- 为了支持像容器这样具有
begin()
成员函数的范围类型,我们总是可以调用成员函数begin()
:
obj.begin(); // 仅当提供了成员函数begin()时有效
然而,对于只有独立begin()
函数(如原生数组)的类型,这种方式不起作用。
- 不幸的是,如果我们使用独立的
begin()
,又会出现问题:- 标准的用于原生数组的独立
std::begin()
需要使用std::
进行完全限定。 - 其他非标准范围不能使用完全限定的
std::
。例如:
- 标准的用于原生数组的独立
class MyColl {
// ...
};
// ... begin(MyColl); // 声明MyColl的独立begin()函数
MyColl obj;
std::begin(obj); // std::begin()找不到::begin(MyType)
2
3
4
5
6
- 必要的解决方法是在调用
begin()
之前添加一个using
声明,并且调用本身不使用限定符:
using std::begin;
begin(obj); // 正确,在所有这些情况下都有效
2
新的std::ranges::begin()
没有这个问题:
std::ranges::begin(obj); // 正确,在所有这些情况下都有效
技巧在于,begin
不是一个使用实参依赖查找的函数,而是一个实现了所有可能查找方式的函数对象。完整示例请查看lib/begin.cpp
。
这适用于std::ranges
中定义的所有实用函数。因此,使用std::ranges
实用函数的代码通常支持更多类型和更复杂的用例。
# 7.4.2 处理迭代器的函数
表7.4“处理迭代器的通用函数”列出了所有用于移动迭代器、向前或向后查看以及计算迭代器之间距离的通用函数。请注意,下一小节将介绍用于交换和移动迭代器所指向元素的函数。
函数 | 含义 |
---|---|
std::ranges::distance(from, to) std::ranges::distance(rg) std::ranges::next(pos) std::ranges::next(pos, n) std::ranges::next(pos, to) std::ranges::next(pos, n, maxpos) std::ranges::prev(pos) std::ranges::prev(pos, n) std::ranges::prev(pos, n, minpos) std::ranges::advance(pos, n) std::ranges::advance(pos, to) std::ranges::advance(pos, n, maxpos) | 返回from 和to 之间的距离(元素个数)返回 rg 中的元素个数(即使对于没有size() 成员函数的范围也能返回大小)返回 pos 后面下一个元素的位置返回 pos 后面第n 个元素的位置返回 pos 后面的to 位置返回 pos 之后但不超过maxpos 的第n 个元素的位置返回 pos 前面元素的位置返回 pos 前面第n 个元素的位置返回 pos 前面但不早于minpos 的第n 个元素的位置将 pos 向前/向后移动n 个元素将 pos 向前移动到to 将 pos 向前/向后移动n 个元素,但不超过maxpos |
表7.4 处理迭代器的通用函数
同样,你应该优先使用这些工具,而不是std
命名空间中相应的传统工具。例如,与std::ranges::next()
不同,旧的工具std::next()
要求为传递的迭代器It
提供std::iterator_traits<It>::difference_type
。然而,一些视图类型的内部迭代器不支持迭代器特性,所以如果你使用std::next()
,代码可能无法编译。
# 7.4.3 交换和移动元素/值的函数
范围库还提供了用于交换和移动值的函数。这些函数列在表7.5“交换和移动元素/值的通用函数”中。它们不仅可用于范围。
函数 | 含义 |
---|---|
std::ranges::swap(val1, val2) std::ranges::iter_swap(pos1, pos2) std::ranges::iter_move(pos) | 交换val1 和val2 的值(使用移动语义)交换迭代器 pos1 和pos2 所指向的值(使用移动语义)返回迭代器 pos 所指向的值并进行移动 |
表7.5 交换和移动元素/值的通用函数
函数std::ranges::swap()
在<concepts>
中定义(std::swap()
在<utility>
中定义),因为标准概念std::swappable
和std::swappable_with
使用它。函数std::ranges::iter_swap()
和std::ranges::iter_move()
在<iterator>
中定义。
std::ranges::swap()
解决了在泛型代码中调用std::swap()
时可能找不到为特定类型提供的最佳交换函数的问题(类似于begin()
和cbegin()
存在的问题)。然而,由于存在通用的后备方案,这种修复并不能解决原本无法编译的代码问题。相反,它可能只是提高了性能。
考虑以下示例:
// lib/swap.cpp
#include <iostream>
#include <utility> //for std::swap()
#include <concepts> //for std::ranges::swap()
struct Foo {
Foo() = default;
Foo(const Foo&) {
std::cout << " COPY constructor\n";
}
Foo& operator=(const Foo&) {
std::cout << " COPY assignment\n";
return *this;
}
void swap(Foo&) {
std::cout << " efficient swap()\n"; // 交换指针,不交换数据
}
};
void swap(Foo& a, Foo& b) {
a.swap(b);
}
int main() {
Foo a, b;
std::cout << "--- std::swap()\n";
std::swap(a, b); // 调用通用的swap
std::cout << "--- swap() after using std::swap\n";
using std::swap;
swap(a, b); // 调用高效的swap
std::cout << "--- std::ranges::swap()\n";
std::ranges::swap(a, b); // 调用高效的swap
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
该程序的输出如下:
--- std::swap()
COPY constructor
COPY assignment
COPY assignment
--- swap() after using std::swap
efficient swap()
--- std::ranges::swap()
efficient swap()
2
3
4
5
6
7
8
请注意,当在std
命名空间中为未在std
中定义的类型定义了swap
函数(这在形式上是不允许的)时,使用std::ranges::swap()
会适得其反:
class Foo {
...
};
namespace std {
void swap(Foo& a, Foo& b) { // std::ranges::swap()找不到这个函数
...
}
}
2
3
4
5
6
7
8
你应该修改这段代码。最好的选择是使用“隐藏友元”:
class Foo {
...
friend void swap(Foo& a, Foo& b) { // std::ranges::swap()能找到这个函数
...
}
};
2
3
4
5
6
函数std::ranges::iter_move()
和std::ranges::iter_swap()
相比于解引用迭代器并调用std::move()
或swap()
有一个优点:它们与代理迭代器(proxy iterators)配合良好。因此,为了在泛型代码中支持代理迭代器,应使用:
auto val = std::ranges::iter_move(it);
std::ranges::iter_swap(it1, it2);
2
而不是:
auto val = std::move(*it);
using std::swap;
swap(*it1, *it2);
2
3
# 7.4.4 值比较函数
表7.6“比较标准的通用函数”列出了现在可以用作比较标准的所有范围实用工具。它们在<functional>
中定义。
函数 | 含义 |
---|---|
std::ranges::equal_to(val1, val2) std::ranges::not_equal_to(val1, val2) std::ranges::less(val1, val2) std::ranges::greater(val1, val2) std::ranges::less_equal(val1, val2) std::ranges::greater_equal(val1, val2) | 返回val1 是否等于val2 返回 val1 是否不等于val2 返回 val1 是否小于val2 返回 val1 是否大于val2 返回 val1 是否小于或等于val2 返回 val1 是否大于或等于val2 |
表7.6 比较标准的通用函数
在检查相等性时,使用std::equality_comparable_with
概念。
在检查顺序时,使用std::totally_ordered_with
概念。请注意,这并不要求必须支持operator<=>
。还要注意,类型不必支持全序关系。只要能够使用<
、>
、<=
和>=
比较值就足够了。然而,这是一个语义约束,无法在编译时检查。
请注意,C++20还引入了函数对象类型std::compare_three_way
,你可以使用它来调用新的operator<=>
。
# 7.5 处理范围的新类型函数/实用工具
本节列出了作为C++20一部分的范围库提供的所有新的辅助类型函数和实用工具。
与通用辅助函数一样,其中一些实用工具在C++20之前就已存在,在std
命名空间中名称相同或略有不同(为了向后兼容仍然提供)。然而,范围实用工具通常为指定功能提供更好的支持。它们可能修复了旧版本存在的缺陷,或者使用概念来限制其使用。
# 7.5.1 范围的通用类型
表7.7“使用范围时产生相关类型的通用函数”列出了范围的所有通用类型定义。它们被定义为别名模板。
类型函数 | 含义 |
---|---|
std::ranges::iterator_t<Rg> std::ranges::sentinel_t<Rg> std::ranges::range_value_t<Rg> std::ranges::range_reference_t<Rg> std::ranges::range_difference_t<Rg> std::ranges::range_size_t<Rg> std::ranges::range_rvalue_reference_t<Rg> std::ranges::borrowed_iterator_t<Rg> std::ranges::borrowed_subrange_t<Rg> | 遍历Rg 的迭代器类型(begin() 返回的类型)Rg 的结束迭代器类型(end() 返回的类型)范围中元素的类型 元素类型的引用类型 两个迭代器之间差值的类型 size() 函数返回值的类型元素类型的右值引用类型 对于借用范围,为 std::ranges::iterator_t<Rg> ;否则为std::ranges::dangling 借用范围的迭代器类型的子范围类型;否则为 std::ranges::dangling |
表7.7 使用范围时产生相关类型的通用函数
如在范围介绍中所述,这些类型函数的主要优点是它们对所有类型的范围和视图都通用。这甚至适用于原始数组。
请注意,std::ranges::range_value_t<Rg>
只是std::iter_value_t<std::ranges::iterator_t<Rg>>
的快捷方式。应优先使用它,而不是Rg::value_type
。
# 7.5.2 迭代器的通用类型
范围库还为迭代器引入了新的类型特性。请注意,这些特性不是在命名空间std::ranges
中定义的,而是在命名空间std
中定义的。
“使用迭代器时产生相关类型的通用函数”表列出了迭代器的所有通用类型特性。它们被定义为别名模板。
类型函数 | 含义 |
---|---|
std::iter_value_t<It> std::iter_reference_t<It> std::iter_rvalue_reference_t<It> std::iter_common_reference_t<It> std::iter_difference_t<It> | 迭代器所指向的值的类型 值类型的引用类型 值类型的右值引用类型 引用类型和值类型引用的公共类型 两个迭代器之间差值的类型 |
表7.8 使用迭代器时产生相关类型的通用函数
由于对新迭代器类别的支持更好,你应该优先使用这些工具,而不是传统的迭代器特性(std::iterator_traits<>
)。
这些函数将在关于迭代器特性的部分详细介绍。
# 7.5.3 新的函数类型
“新的函数类型”表列出了可用作辅助函数的新类型。它们在<functional>
中定义。
类型函数 | 含义 |
---|---|
std::identity std::compare_three_way | 一个返回自身的函数对象 一个调用 <=> 运算符的函数对象 |
表7.9新的函数类型
函数对象std::identity
通常用于在可以传递投影的地方传递“无投影”。如果一个算法支持传递投影:
auto pos = std::ranges::find(coll, 25, //查找值25
[](auto x) {return x*x;});//对于平方后的元素
2
使用std::identity
可以让程序员传递“无投影”:
auto pos = std::ranges::find(coll, 25, //查找值25
std::identity{}); //对于元素本身
2
该函数对象被用作默认模板参数,用于声明一个可以省略投影的算法:
template<std::ranges::input_range R, typename T,
typename Proj = std::identity>
constexpr std::ranges::borrowed_iterator_t<R> find(R&& r, const T& value, Proj proj = {});
2
3
函数对象类型std::compare_three_way
是一个新的函数对象类型,用于指定应调用/使用新的<=>
运算符(就像std::less
或std::ranges::less
代表调用<
运算符一样)。它在关于<=>
运算符的章节中进行了介绍。
# 7.5.4 处理迭代器的其他新类型
“处理迭代器的其他新类型”表列出了处理迭代器的所有新类型。它们在<iterator>
中定义。
类型函数 | 含义 |
---|---|
std::incrementable_traits<It> std::projected<It1 ,It2> | 用于产生两个迭代器的difference_type 的辅助类型用于为投影制定约束的类型 |
表7.10处理迭代器的其他新类型
还要注意新的迭代器和哨兵类型。
# 7.6 范围算法
在C++20中,支持将范围作为单个参数传递,并能够处理不同类型的哨兵(尾后迭代器),这为调用算法提供了新的方式。
然而,它们也有一些限制,并非所有算法都支持将范围作为一个整体进行传递。
# 7.6.1 范围算法的优点和限制
对于一些算法,目前还没有允许程序员将范围作为单个参数传递的范围版本:
- 范围算法不支持并行执行(C++17引入)。没有接受范围参数作为单个对象和执行策略的API。
- 目前还没有针对单个对象范围的数值算法。要调用像
std::accumulate()
这样的算法,仍然需要传递范围的起始和结束位置。C++23可能会提供数值范围算法。
如果算法支持范围,它们会使用概念在编译时发现可能的错误:
- 迭代器和范围的概念确保你传递有效的迭代器和范围。
- 可调用对象的概念确保你传递有效的辅助函数。
此外,返回类型可能会有所不同,原因如下:
- 它们支持并可能返回不同类型的迭代器,为此定义了特殊的返回类型。这些类型列在“范围算法的新返回类型”表中。
- 它们可能返回借用的迭代器,这表明由于传递了临时范围(右值),该迭代器不是有效的迭代器。
类型 | 含义 | 成员 |
---|---|---|
std::ranges::in_in_result std::ranges::in_out_result | 用于两个输入范围的位置 用于一个输入范围的一个位置和一个输出范围的一个位置 | in1 ,in2 in ,out |
std::ranges::in_in_out_result | 用于两个输入范围的位置和一个输出范围的一个位置 | in1 ,in2 ,out |
std::ranges::in_out_out_result | 用于一个输入范围的一个位置和两个输出范围的位置 | in ,out1 ,out2 |
std::ranges::in_fun_result | 用于一个输入范围的一个位置和一个函数 | in ,out |
std::ranges::min_max_result | 用于一个最大值和一个最小值的位置/值 | min ,max |
std::ranges::in_found_result | 用于一个输入范围的一个位置和一个布尔值 | in ,found |
表7.11范围算法的新返回类型
下面的程序展示了如何使用这些返回类型:
// ranges/results.cpp
#include <iostream>
#include <string_view>
#include <vector>
#include <algorithm>
void print(std::string_view msg, auto beg, auto end) {
std::cout << msg;
for (auto pos = beg; pos != end; ++pos) {
std::cout << " " << *pos;
}
std::cout << "\n";
}
int main() {
std::vector inColl{1, 2, 3, 4, 5, 6, 7};
std::vector outColl{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto result = std::ranges::transform(inColl, outColl.begin(), [](auto val) {
return val * val;
});
print("processed in: ", inColl.begin(), result.in);
print("rest of in: ", result.in, inColl.end());
print("written out: ", outColl.begin(), result.out);
print("rest of out: ", result.out, outColl.end());
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
该程序的输出如下:
processed in: 1 2 3 4 5 6 7
rest of in:
written out: 1 4 9 16 25 36 49
rest of out: 8 9 10
2
3
4
# 7.6.2 算法概述
所有算法的情况变得越来越复杂。有些算法可以并行使用,有些则不行。有些算法得到了范围库的支持,有些则没有。本节概述了哪些算法以何种形式可用(但不会详细介绍每个算法的具体功能)。
最后三列的含义如下:
- “Ranges”表示该算法是否受范围库支持。
- “_result”表示是否使用以及使用哪些
_result
类型(例如,in_out
代表in_out_result
)。 - “Borrowed”表示该算法是否返回借用的迭代器。
“非修改算法”表列出了C++20中可用的非修改标准算法。
名称 | 引入时间 | 是否支持并行 | 是否受范围库支持 | _result | 是否返回借用迭代器 |
---|---|---|---|---|---|
for_each() | C++98 | 是 | 是 | in_fun | 是 |
for_each_n() | C++17 | 是 | 是 | in_fun | - |
count() | C++98 | 是 | 是 | - | - |
count_if() | C++98 | 是 | 是 | - | - |
min_element() | C++98 | 是 | 是 | 是 | - |
max_element() | C++98 | 是 | 是 | 是 | - |
minmax_element() | C++11 | 是 | 是 | min_max | 是 |
min() | C++20 | 否 | 仅支持 | - | - |
max() | C++20 | 否 | 仅支持 | - | - |
minmax() | C++20 | 否 | 仅支持 | min_max | - |
find() | C++98 | 是 | 是 | - | 是 |
find_if() | C++98 | 是 | 是 | - | 是 |
find_if_not() | C++11 | 是 | 是 | - | 是 |
search() | C++98 | 是 | 是 | - | 是 |
search_n() | C++98 | 是 | 是 | - | 是 |
find_end() | C++98 | 是 | 是 | - | 是 |
find_first_of() | C++98 | 是 | 是 | - | 是 |
adjacent_find() | C++98 | 是 | 是 | - | 是 |
equal() | C++98 | 是 | 是 | - | - |
is_permutation() | C++11 | 否 | 是 | - | - |
mismatch() | C++98 | 是 | 是 | in_in | 是 |
lexicographical_compare() | C++98 | 是 | 是 | - | - |
lexicographical_compare_three_way() | C++20 | 否 | 否 | - | - |
is_sorted() | C++11 | 是 | 是 | - | - |
is_sorted_until() | C++11 | 是 | 是 | - | 是 |
is_partitioned() | C++11 | 是 | 是 | - | - |
partition_point() | C++11 | 否 | 是 | - | 是 |
is_heap() | C++11 | 是 | 是 | - | - |
is_heap_until() | C++11 | 是 | 是 | - | 是 |
all_of() | C++11 | 是 | 是 | - | - |
any_of() | C++11 | 是 | 是 | - | - |
none_of() | C++11 | 是 | 是 | - | - |
表7.12 非修改算法
请注意,std::ranges::min()
、std::ranges::max()
和std::ranges::minmax()
算法在std
中仅存在接受两个值或std::initializer_list<>
的对应版本。
“修改算法”表列出了C++20中可用的修改标准算法。
名称 | 引入时间 | 是否支持并行 | 是否受范围库支持 | _result | 是否返回借用迭代器 |
---|---|---|---|---|---|
for_each() | C++98 | 是 | 是 | in_fun | 是 |
for_each_n() | C++17 | 是 | 是 | in_fun | - |
copy() | C++98 | 是 | 是 | in_out | 是 |
copy_if() | C++98 | 是 | 是 | in_out | - |
copy_n() | C++11 | 是 | 是 | in_out | 是 |
copy_backward() | C++11 | 否 | 是 | in_out | 是 |
move() | C++11 | 是 | 是 | in_out | 是 |
move_backward() | C++11 | 否 | 是 | in_out | 是 |
transform() (单范围) | C++98 | 是 | 是 | in_out | 是 |
transform() (双范围) | C++98 | 是 | 是 | in_in_out | 是 |
merge() | C++98 | 是 | 是 | - | - |
swap_ranges() | C++98 | 是 | 是 | in_in | 是 |
fill() | C++98 | 是 | 是 | - | 是 |
fill_n() | C++98 | 是 | 是 | - | - |
generate() | C++98 | 是 | 是 | - | 是 |
generate_n() | C++98 | 是 | 是 | - | - |
iota() | C++11 | 否 | 否 | - | - |
replace() | C++98 | 是 | 是 | - | 是 |
replace_if() | C++98 | 是 | 是 | - | 是 |
replace_copy() | C++98 | 是 | 是 | in_out | 是 |
replace_copy_if() | C++98 | 是 | 是 | in_out | 是 |
表7.13 修改算法
“移除算法”表列出了C++20中可用的“移除”标准算法。请注意,算法实际上从不真正移除元素。当它们将整个范围作为单个参数时,情况依然如此。它们会改变顺序,将未移除的元素移到前面,并返回新的末尾位置。
名称 | 引入时间 | 是否支持并行 | 是否受范围库支持 | _result | 是否返回借用迭代器 |
---|---|---|---|---|---|
remove() | C++98 | 是 | 是 | - | 是 |
remove_if() | C++98 | 是 | 是 | - | 是 |
remove_copy() | C++98 | 是 | 是 | in_out | 是 |
remove_copy_if() | C++98 | 是 | 是 | in_out | 是 |
unique() | C++98 | 是 | 是 | - | 是 |
unique_copy() | C++98 | 是 | 是 | in_out | 是 |
表7.14 移除算法
“变异算法”表列出了C++20中可用的变异标准算法。
名称 | 引入时间 | 是否支持并行 | 是否受范围库支持 | _result | 是否返回借用迭代器 |
---|---|---|---|---|---|
reverse() | C++98 | 是 | 是 | - | 是 |
reverse_copy() | C++98 | 是 | 是 | in_out | 是 |
rotate() | C++98 | 是 | 是 | - | 是 |
rotate_copy() | C++98 | 是 | 是 | in_out | 是 |
shift_left() | C++20 | 是 | 否 | - | - |
shift_right() | C++20 | 是 | 否 | - | - |
sample() | C++17 | 否 | 是 | - | - |
next_permutation() | C++98 | 否 | 是 | in_found | 是 |
prev_permutation() | C++98 | 否 | 是 | in_found | 是 |
shuffle() | C++11 | 否 | 是 | - | 是 |
random_shuffle() | C++98 | 否 | 否 | - | - |
partition() | C++98 | 是 | 是 | - | - |
stable_partition() | C++98 | 是 | 是 | - | - |
partition_copy() | C++11 | 是 | 是 | - | - |
表7.15 变异算法
“排序算法”表列出了C++20中可用的排序标准算法。
名称 | 引入时间 | 是否支持并行 | 是否受范围库支持 | _result | 是否返回借用迭代器 |
---|---|---|---|---|---|
sort() | C++98 | 是 | 是 | - | 是 |
stable_sort() | C++98 | 是 | 是 | - | 是 |
partial_sort() | C++98 | 是 | 是 | - | 是 |
partial_sort_copy() | C++98 | 是 | 是 | in_out | 是 |
nth_element() | C++98 | 是 | 是 | - | 是 |
partition() | C++98 | 是 | 是 | - | 是 |
stable_partition() | C++98 | 是 | 是 | - | 是 |
partition_copy() | C++11 | 是 | 是 | in_out_out | 是 |
make_heap() | C++98 | 否 | 是 | - | 是 |
push_heap() | C++98 | 否 | 是 | - | 是 |
pop_heap() | C++98 | 否 | 是 | - | 是 |
sort_heap() | C++98 | 否 | 是 | - | 是 |
表7.16 排序算法
“针对已排序范围的算法”表列出了C++20中可用的针对已排序范围的标准算法。
名称 | 引入时间 | 是否支持并行 | 是否受范围库支持 | _result | 是否返回借用迭代器 |
---|---|---|---|---|---|
binary_search() | C++98 | 否 | 是 | - | - |
includes() | C++98 | 是 | 是 | - | - |
lower_bound() | C++98 | 否 | 是 | - | 是 |
upper_bound() | C++98 | 否 | 是 | - | 是 |
equal_range() | C++98 | 否 | 是 | - | 是 |
merge() | C++98 | 是 | 是 | in_in_out | 是 |
inplace_merge() | C++98 | 是 | 是 | - | 是 |
set_union() | C++98 | 是 | 是 | in_in_out | 是 |
set_intersection() | C++98 | 是 | 是 | in_in_out | 是 |
set_difference() | C++98 | 是 | 是 | in_out | 是 |
set_symmetric_difference() | C++98 | 是 | 是 | in_in_out | 是 |
partition_point() | C++11 | 否 | 是 | - | 是 |
表7.17 针对已排序范围的算法
“数值算法”表列出了C++20中可用的数值标准算法。
名称 | 引入时间 | 是否支持并行 | 是否受范围库支持 | _result | 是否返回借用迭代器 |
---|---|---|---|---|---|
accumulate() | C++98 | 否 | 否 | - | - |
reduce() | C++17 | 是 | 否 | - | - |
transform_reduce() | C++17 | 是 | 否 | - | - |
inner_product() | C++98 | 否 | 否 | - | - |
adjacent_difference() | C++98 | 否 | 否 | - | - |
partial_sum() | C++98 | 否 | 否 | - | - |
inclusive_scan() | C++17 | 是 | 否 | - | - |
exclusive_scan() | C++17 | 是 | 否 | - | - |
transform_inclusive_scan() | C++17 | 是 | 否 | - | - |
transform_exclusive_scan() | C++17 | 是 | 否 | - | - |
iota() | C++11 | 否 | 否 | - | - |
表7.18 数值算法