Does the range-based 'for' loop deprecate many simple algorithms?

The first version

std::generate(numbers.begin(), numbers.end(), rand);

tells us that you want to generate a sequence of values.

In the second version the reader will have to figure that out himself.

Saving on typing is usually suboptimal, as it is most often lost in reading time. Most code is read a lot more than it is typed.


Whether the for loop is range based or not does not make a difference at all, it only simplifies the code inside the parenthesis. Algorithms are clearer in that they show the intent.


Personally, my initial reading of:

std::generate(numbers.begin(), numbers.end(), rand);

is "we're assigning to everything in a range. The range is numbers. The values assigned are random".

My initial reading of:

for (int& x : numbers) x = rand();

is "we're doing something to everything in a range. The range is numbers. What we do is assign a random value."

Those are pretty darn similar, but not identical. One plausible reason I might want to provoke the first reading, is because I think the most important fact about this code is that it assigns to the range. So there's your "why would I want to...". I use generate because in C++ std::generate means "range assignment". As btw does std::copy, the difference between the two is what you're assigning from.

There are confounding factors, though. Range-based for loops have an inherently more direct way of expressing that the range is numbers, than iterator-based algorithms do. That's why people work on range-based algorithm libraries: boost::range::generate(numbers, rand); looks better than the std::generate version.

As against that, int& in your range-based for loop is a wrinkle. What if the value type of the range isn't int, then we're doing something annoyingly subtle here that depends on it being convertible to int&, whereas the generate code only depends on the return from rand being assignable to the element. Even if the value type is int, I still might stop to think about whether it is or not. Hence auto, which defers thinking about the types until I see what gets assigned -- with auto &x I say "take a reference to the range element, whatever type that might have". Back in C++03, algorithms (because they're function templates) were the way to hide exact types, now they're a way.

I think it has always been the case that the simplest algorithms have only a marginal benefit over the equivalent loops. Range-based for loops improve loops (primarily by removing most of the boilerplate, although there's a little more to them than that). So the margins draw tighter and perhaps you change your mind in some specific cases. But there's a still a style difference there.


In my opinion, Effective STL Item 43: "Prefer algorithm calls to hand-written loops." is still a good advice.

I usually write wrapper functions to get rid of the begin() / end() hell. If you do that, your example will look like this:

my_util::generate(numbers, rand);

I believe it beats the range based for loop both in communicating the intent and in readability.


Having said that, I must admit that in C++98 some STL algorithm calls yielded unutterable code and following "Prefer algorithm calls to hand-written loops" did not seem like a good idea. Luckily, lambdas have changed that.

Consider the following example from Herb Sutter: Lambdas, Lambdas Everywhere.

Task: Find first element in v that is > x and < y.

Without lambdas:

auto i = find_if( v.begin(), v.end(),
bind( logical_and<bool>(),
bind(greater<int>(), _1, x),
bind(less<int>(), _1, y) ) );

With lambda

auto i=find_if( v.begin(), v.end(), [=](int i) { return i > x && i < y; } );

In my opinion, the manual loop, though might reduce verbosity, lacks readabitly:

for (int& x : numbers) x = rand();

I would not use this loop to initialize1 the range defined by numbers, because when I look at it, it seems to me that it is iterating over a range of numbers, but in actuality it does not (in essence), i.e instead of reading from the range, it is writing to the range.

The intent is much clearer when you use std::generate.

1. initialize in this context means to give meaningful value to the elements of the container.