With an input iterator, I can consume different sub-ranges out of it, e.g.
void test(std::input_iterator auto it) {
while (*it < 1) ++it; // drop_while
int n = *it; ++it; // take(1)
int sum = 0; for (; n-- > 0; ++it) sum += (1 + *it); // fold_left(take(n) | transform)
int prd = 1; for (; int x = *it / 2; ++it) prd *= x; // fold_left(transform | take_while)
std::cout << sum << ' ' << prd << '\n';
}
int main() {
test(std::begin({0, 0, 0, 3, 400, 30, 2, 4, 6, 0}));
}
Is there a way to do the same with std::ranges/std::views?
Generally speaking, no. At least it won't conform with std::ranges::view_interface nor std::ranges::range_adaptor_closure
Essentially all the views / adaptors you mentioned requires the std::ranges::viewable_range concept, which forbids any lvalue input_range, implying you must handover ownership to the adaptors used which could not be splited across all.
It should be noted that OP provided an array as test range, which is a forward_range and less strict than input_range, i.e. it supports multi-pass instead of single-pass. A better test would be ...
std::generator<int> test_range() {
static constexpr auto data = {0, 0, 0, 3, 400, 30, 2, 4, 6, 0};
for(int num: data) {
co_yield num;
}
}
int main() {
auto data = test_range();
static_assert(std::ranges::input_range<decltype(data)>);
test(data);
}
This ought not be interpreted as std::ranges / std::views is less powerful than <algorithm> or <numeric> though, since std::accumulate(it, it+n, ...) creates a copy of the iterator.
Never the less OP have raised a good example that managing your own iterator stays more powerful than depending on STL utilities.
The problem is that in algo(range | view_with_a_sentinel), the algorithm doesn't receive the original range, so it cannot return the unconsumed part of the range.
However, with all those views, and ranges, deep inside there is an iterator which gets incremented. If we get this iterator, we can construct another range.
So the answer is: use indirection.
Let's write an input iterator that has a reference to another input iterator.
template<std::input_iterator I>
struct IterProxy : std::iterator_traits<I> {
std::reference_wrapper<I> it{};
IterProxy(I& it) : it{it} {}
using iterator_concept = std::input_iterator_tag;
auto operator*() const { return *it.get(); }
auto& operator++() { ++it.get(); return *this; }
void operator++(int) { ++it.get(); }
friend auto operator<=>(IterProxy, IterProxy) = default;
friend auto operator==(IterProxy p, I i) { return p.it.get() == i; }
friend auto operator==(I i, IterProxy p) { return p.it.get() == i; }
};
Now we can use this proxy instead of an actual range, so we would always keep track of an original input iterator:
void test_ranges(std::ranges::input_range auto range) {
auto it{std::ranges::begin(range)}, last{std::ranges::end(range)};
auto tail = std::ranges::subrange(IterProxy{it}, last);
algo1(tail | view | ...);
algo2(tail | view | ...);
}
Here is the code from the question, with ranges:
void test_ranges(std::ranges::input_range auto range) {
auto it{std::ranges::begin(range)}, last{std::ranges::end(range)};
auto tail = std::ranges::subrange(IterProxy{it}, last);
int n = *std::ranges::begin(tail | std::views::drop_while([](auto x) { return x < 1; }));
++it; // could be begin(tail | take(1))
int sum = std::ranges::fold_left(
tail | std::views::take(n) | std::views::transform([](auto x) { return x + 1; }),
0, [](auto sum, auto x) { return sum + x; });
int prd = std::ranges::fold_left(
tail | std::views::transform([](auto x) { return x / 2; }) | std::views::take_while([](auto x) { return x != 0; }),
1, [](auto prd, auto x) { return prd * x; });
std::cout << sum << ' ' << prd << '\n';
}
int main() {
std::istringstream in{"0 0 0 3 400 30 2 4 6 0"};
std::istream_iterator<int> first{in}, last;
test_ranges(std::ranges::subrange{first, last});
}
https://godbolt.org/z/4zGscYvs4
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With