Range-based for loops are syntactic sugar over a traditional loop with iterators. They make the previous idiom simpler and also enable some new ones.
No one would dare use boost::irange
in a traditional loop. But with the new loop, it’s not that unwieldy:
for(int i : boost::irange(0, 10)) {
std::cout << i << '\n';
}
// prints: 0 1 2 3 4 5 6 7 8 9
… or in reverse:
for(int i : boost::irange(0, 10) | boost::adaptors::reversed) {
std::cout << i << ' ';
}
// prints: 9 8 7 6 5 4 3 2 1 0
boost::optional
can be seen as a container that holds at most one element.
So, why not loop over it? Just provide appropriate iterators and begin
and end
functions.
boost::optional<T> o = 42;
for(auto&& t : o) {
frob(t);
schizzle(t);
}
The code in the loop would run only when the optional holds a value (and only once), and t
is automatically bound to it.
One interesting property of the range-based loop is that, if the range is a temporary, its lifetime is the whole loop (all iterations). This allows us to throw RAII semantics into mix. And what interesting things can we do with this? Naturally enforced and properly scoped locks!
The idea is to, instead of keeping a mutex and the shared data it protects both visible and separate,
we hide the data away and only reveal it when the mutex is acquired. We write a class with the mutex
and data as private members and add an open
function that acquires the mutex and returns an object that releases it upon destruction.
Add the appropriate iterators to that, and we can write the following:
locker_box<racy> box; // shared data is not accessible without locking
try {
for(auto&& x : box.open()) { // lock is acquired, and is held only while the loop runs
// and the shared data becomes available through x
x.do_racy_stuff();
x.do_more_racy_stuff();
x.do_really_raunchy_stuff();
}
} catch(not_suitable_for_this_audience const&) {}