I regularly encounter situations in my code in which I would like to iterate over a range in direct or in reverse order depending on a runtime condition. This typically results in code like the following
if (reverse) {
using boost::adaptors::reversed;
for (auto const & x : range | reversed) do_stuff(x);
} else {
for (auto const & x : range) do_stuff(x);
}
or
std::vector<Foo> v(range.begin(), range.end());
if (reverse) boost::range::reverse(v);
for (auto const & x : v) do_stuff(x);
which contains code duplication (first one) or is inefficient (second one).
I've been thinking many times about an hypothetical Boost range adaptor that would conditionally reverse a range, so that I could write
using boost::adaptors::reversed_if;
for (auto const & x : range | reversed_if(reverse)) do_stuff(x);
I can implement it myself (starting from here) but I am not sure about how to proceed. In order to support runtime conditions, I'm afraid I'll have to check a boolean at each iteration to determine the iteration direction or use virtuality to dispatch to iteration code. Is this the reason why this is not provided in Boost range adaptors?
Any alternative solution?
If you want to avoid runtime checking at each increment which way to go, you have to convert the runtime value into a compile time value outside the structure of the loop.
In this case, we want the range we are looping over to vary, while the body does not.
The easy way to do this is to write a lambda for the body, then have a switch to pick which loop to choose.
we have done the runtime dispatch outside of the loop, creating two different loops with static type information about how they loop.
We can make an adapter like this:
where
magic_switch
takes an index (std::size_t
) as its first argument. It returns a lambda, which takes a list of arguments. It returns a lambda, which takes a lambda and passes to it the argument from the 2nd list as determined by the first argument's index into that list.is a sketch at an implementation (it almost certainly contains build errors).
The hard part is that "type flow" (to coin a term) doesn't go the way you usually want it to. So I'm forced to use continuation-passing-style basically.
Note that many compilers are unhappy with a pack expansion containing an entire lambda. A helper function that returns the function pointer can be written:
then replace the table with:
on those compilers.