Taken from range-v3 docs, the following example demonstrates a simple composition of views
pipelined to produce a range
:
std::vector<int> const vi{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
using namespace ranges;
auto rng = vi | views::remove_if([](int i){return i % 2 == 1;})
| views::transform([](int i){return std::to_string(i);});
I know that views::foo
is equivalent to something like foo_view()
and therefore the example above ends up like:
transform_view(remove_if_view(vi, <lambda>), <lambda>)
Now the question is:
How is the sequence of the remove_if
and transform
operations taking place? (I know that they are lazy and they are not actually calculated at this step, rather than later on when rng
is materialized, but that's not the point).
I can see two options here:
The operations are fused by range-v3 and when a given element of
rng
is accessed through some iterator, the two operations are applied to the element consequently.When a given element is requested, the whole
remove_if
operation is applied throughoutvi
and then the output-vector of that operation is fed intotransform
. Thus, we end up with a whole "trasformed + removed_if" vector that gives us access to the desired element.
I am pretty sure that option (1) is what's actually going on. If that's the case, how does range-v3 accomplish that? Does it have some kind of generic composition code in order to combine unlimited number of composed-by-views operations?
Side question: What kind of iterator do range-v3 views expose? I think that whatever below a random-access
iterator would render the parallelization impossible.
Meta-question: If option (1) is the truth, then isn't it extremely trivial to parallelize range-algorithms
given that they take as input a simple range (composed by multiple views, calculated on-demand with operation fusion)?