从开始到 C++17,<algorithm> 中的所有内容都基于迭代器对:您有一个 iterator 指代范围的开头,一个 iterator 指代范围的结尾,始终具有相同的类型。
在 C++20 中,这是通用的。范围现在由 iterator 和该迭代器的 sentinel 表示 - 其中 sentinel 本身实际上不需要是任何类型的迭代器,它只需要是可以比较等于其相应迭代器的类型(这是sentinel_for 的概念)。
C++17 范围往往是† 有效的 C++20 范围,但不一定是相反的方向。一个原因是能够拥有一个独特的 sentinel 类型,但还有其他的,这也涉及到这个问题(见下文)。
为了配合新模型,C++20 在std::ranges 命名空间中添加了大量算法,这些算法采用iterator 和sentinel,而不是两个iterators。例如,虽然我们一直有:
template<class InputIterator, class T>
constexpr InputIterator find(InputIterator first, InputIterator last,
const T& value);
我们现在也有:
namespace ranges {
template<input_iterator I, sentinel_for<I> S, class T, class Proj = identity>
requires indirect_binary_predicate<ranges::equal_to, projected<I, Proj>, const T*>
constexpr I find(I first, S last, const T& value, Proj proj = {});
template<input_range R, class T, class Proj = identity>
requires indirect_binary_predicate<ranges::equal_to,
projected<iterator_t<R>, Proj>, const T*>
constexpr borrowed_iterator_t<R>
find(R&& r, const T& value, Proj proj = {});
}
这里的第一个重载采用 iterator/sentinel 对,而第二个重载采用范围。
虽然许多算法在std::ranges 中添加了相应的重载,但<numeric> 中的重载被忽略了。有std::accumulate,但没有std::ranges::accumulate。因此,我们目前唯一可用的版本是采用迭代器对的版本。否则,你可以写:
auto rng = std::ranges::istream_view<int>(std::cin);
std::cout << std::ranges::accumulate(rng, 0);
不幸的是,std::ranges::istream_view 是新的 C++20 范围之一,其标记类型与其迭代器类型不同,因此您也不能将 rng.begin() 和 rng.end() 传递给 std::accumulate。
这通常给您留下两个选择(三个,如果您包括等待 C++23,希望有一个 std::ranges::fold):
- 编写您自己的基于范围和基于迭代器哨兵的算法。对于
fold,这很容易做到。
或者
- 有一个实用程序可以将 C++20 范围包装成与 C++17 兼容的范围:
views::common。所以你可以这样做:
auto rng = std::ranges::istream_view<int>(ints) | std::views::common;
std::cout << std::accumulate(rng.begin(), rng.end(), 0);
在这种特殊情况下除外。
istream_view 的迭代器是不可复制的,在 C++17 中所有的迭代器都必须是。所以实际上没有办法提供基于istream_view 的兼容 C++17 的迭代器。您需要适当的 C++20 范围支持。未来的std::ranges::fold 将支持只移动视图和只移动迭代器,但std::accumulate 永远不能。
在这种情况下,只留下选项 1。
†C++20 迭代器必须是可默认构造的,这不是 C++17 迭代器的要求。因此,具有不可默认构造的迭代器的 C++17 范围将不是有效的 C++20 范围。