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
rngis accessed through some iterator, the two operations are applied to the element consequently.When a given element is requested, the whole
remove_ifoperation is applied throughoutviand 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)?