From inception up through C++17, everything in <algorithm>
is based on iterator pairs: you have one iterator
referring to the beginning of a range and one iterator
referring to the end of the range, always having the same type.
In C++20, this was generalized. A range is now denoted by an iterator
and a sentinel
for that iterator - where the sentinel
itself need not actually be an iterator of any kind, it just needs to be a type that can compare equal to its corresponding iterator (this is the sentinel_for
concept).
C++17 ranges tend to be† valid C++20 ranges, but not necessarily in the opposite direction. One reason is the ability to have a distinct sentinel
type, but there are others, which also play into this question (see below).
To go along with the new model, C++20 added a large amount of algorithms into the std::ranges
namespace that take an iterator
and a sentinel
, rather than two iterator
s. So for instance, while we've always had:
template<class InputIterator, class T>
constexpr InputIterator find(InputIterator first, InputIterator last,
const T& value);
we now also have:
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 = {});
}
The first overload here takes an iterator
/sentinel
pair and the second takes a range instead.
While a lot of algorithms added corresponding overloads into std::ranges
, the ones in <numeric>
were left out. There is a std::accumulate
but there is no std::ranges::accumulate
. As such, the only version we have available at the moment is one that takes an iterator-pair. Otherwise, you could just write:
auto rng = std::ranges::istream_view<int>(std::cin);
std::cout << std::ranges::accumulate(rng, 0);
Unfortunately, std::ranges::istream_view
is one of the new, C++20 ranges whose sentinel type differs from its iterator type, so you cannot pass rng.begin()
and rng.end()
into std::accumulate
either.
This leaves you with two options generally (three, if you include waiting for C++23, which will hopefully have a std::ranges::fold
):
- Write your own range-based and iterator-sentinel-based algorithms. Which for
fold
is very easy to do.
Or
- There is a utility to wrap a C++20 range into a C++17-compatible one:
views::common
. So you could this:
auto rng = std::ranges::istream_view<int>(ints) | std::views::common;
std::cout << std::accumulate(rng.begin(), rng.end(), 0);
Except not in this specific case.
istream_view
's iterators aren't copyable, and in C++17 all iterators must be. So there isn't really a way to provide C++17-compatible iterators based on istream_view
. You need proper C++20-range support. The future std::ranges::fold
will support move-only views and move-only iterators, but std::accumulate
never can.
Which in this case, just leaves option 1.
†A C++20 iterator needs to be default-constructible, which was not a requirement of C++17 iterators. So a C++17 range with non-default-constructible iterators would not be a valid C++20 range.