为什么我不能使用istream_view和std::accumulate来总结我的输入?
这个程序:
#include <ranges>
#include <numeric>
#include <iostream>
int main() {
auto rng = std::ranges::istream_view<int>(std::cin);
std::cout << std::accumulate(std::ranges::begin(rng), std::ranges::end(rng), 0);
}
应该对标准输入流上显示为文本的所有整数求和。但是 - 它不会编译。我知道std::ranges::begin()并std::ranges::end()存在,所以发生了什么?编译器告诉我它找不到合适的候选人;怎么了?
回答
从一开始到 C++17,in<algorithm>中的所有内容都基于迭代器对:你有一个iterator引用范围的开头和一个iterator引用范围的结尾,总是具有相同的类型。
在 C++20 中,这是通用的。一个范围现在由该迭代器的 aniterator和 asentinel表示 - 其sentinel本身实际上不需要是任何类型的迭代器,它只需要是一种可以与其对应的迭代器进行比较的类型(这就是sentinel_for概念)。
C++17 范围往往是†有效的 C++20 范围,但不一定是相反的方向。一个原因是能够拥有独特的sentinel类型,但还有其他原因也会影响这个问题(见下文)。
为了配合新模型,C++20 在std::ranges命名空间中添加了大量算法,它们采用 aniterator和 a 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++20 范围支持。未来std::ranges::fold将支持仅移动视图和仅移动迭代器,但std::accumulate永远不会。
在这种情况下,只留下选项 1。
† C++20 迭代器需要是默认构造的,这不是 C++17 迭代器的要求。因此,具有不可默认构造的迭代器的 C++17 范围将不是有效的 C++20 范围。
- @einpoklum Given that it prevents you from doing what you want to do, I would classify it as unfortunate, yes. If you don't, that's fine, but don't go around editing people's answers because you have different preferences on word choice -- that's not what editing is for.