Does the range-based for loop describe many simple algorithms?

Algorithm Solution:

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

Outline based solution:

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

Why would I like to use the more verbose std::generate for for-loops in C ++ 11?

+79
c ++ algorithm foreach c ++ 11 stl
Jan 10 '13 at 13:13
source share
10 answers

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 it out for himself.

Saving on input is usually suboptimal, as it is most often lost during reading. Most of the code is read much more than it is typed.

+77
Jan 10 '13 at 13:23
source share
— -

Regardless of whether the for loop is range-based or not, this makes no difference at all, it only simplifies the code inside the bracket. Algorithms are clearer in the sense that they show intent.

+42
Jan 10 '13 at 13:15
source share

Personally, my initial reading:

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

: "We assign everything in the range. The range of numbers . The values ​​assigned are random."

My initial reading:

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

- "we do something in everything in the range. The range of numbers . We do the assignment of a random value."

It is pretty darn similar, but not identical. One of the plausible reasons why I could provoke the first reading is because I think the most important fact in this code is that it assigns to a range. So, "why I want ...". I use generate because in c ++ std::generate means "range assignment". Since btw does std::copy , the difference between the two is what you assign.

However, there are confounding factors. For range-based loops, there is a more direct way to express that range of numbers than iterator-based algorithms. This is why people work on range-based algorithm libraries: boost::range::generate(numbers, rand); Looks better than the std::generate version.

In contrast, int& in your range-based loop is a wrinkle. What if the value type of the range is not int , then we are doing something annoyingly subtle here, which depends on its being converted to int& , while the generate code depends only on the return from rand assigned to the element. Even if the value type is int , I can still stop thinking about whether it is or not. Therefore, auto , which diverts attention to types until I see what is assigned, with auto &x I say: "Take a reference to a range element, no matter what type it is." Back in C ++ 03, algorithms (because they are function templates) were a way to hide exact types, now this is a way.

I think it has always been that simplest algorithms have only a slight advantage over equivalent loops. Ranges for loops improve the contours (basically, removing most of the pattern, although a little more for them). Thus, margins become more stringent and you may change your mind in some specific cases. But there is still a difference in style.

+29
Jan 10 '13 at 14:06
source share

In my opinion, an effective STL. Paragraph 43: "Prefers the algorithm for calling hand loops." is still good advice.

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

 my_util::generate(numbers, rand); 

I believe this is superior to the cycle-based range, both for conveying intent and for readability.




Having said that, I have to admit that in C ++ 98 some calls to the STL algorithm gave unspeakable code, and the following “Calls of the algorithm to manual spelling” did not look very good. Fortunately, lambdas have changed that.

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

Task: Find the first element in v, which is > x and < y .

Without lambda:

 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; } ); 
+22
Jan 10 '13 at
source share

In my opinion, the manual loop, although it can reduce verbosity, does not have readabitly:

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

I would not use this loop to initialize range 1 defined by numbers, because when I look at it, it seems to me that it is iterating over a whole series of numbers, but in fact it is (essentially) i.e. instead of reading from a range, it writes to a range.

Washing is much clearer if you use std::generate .

1. Initialization in this context means providing meaningful value to the elements of the container.

+21
Jan 10 '13 at 13:20
source share

There are some things you cannot do (simply) with range-based loops that can use algorithms that take iterators as input. For example, using std::generate :

Fill the container to limit (excluded, limit is a valid iterator on numbers ) with variables from one distribution, and the rest with variables from another distribution.

 std::generate(numbers.begin(), limit, rand1); std::generate(limit, numbers.end(), rand2); 

Iterative algorithms give you better control over the range in which you work.

+9
Jan 10 '13 at 16:24
source share

In the particular case of std::generate I agree with the previous answers to the question of readability / intent. std :: generate seems to be a more understandable version to me. But I admit that this is to some extent a matter of taste.

However, I have one more reason not to throw out std :: algorithm - there are certain algorithms that are specialized for some data types.

The simplest example would be std::fill . The general version is implemented as a loop through the range provided, and this version will be used when creating the template instance. But not always. For example. if you give it a range of std::vector<int> - often it actually calls memset under the hood, which gives much faster and better code.

So, I'm trying to play a performance card here.

Your handwritten loop may be as fast as the std :: algorithm version, but it can hardly be faster. And what's more, std :: algorithm can be specialized for specific containers and types, and this is done under the clean STL interface.

+6
Jan 11 '13 at 6:27
source share

My answer may not be. If we are talking about C ++ 11, then maybe (I don't like it anymore). For example, std::for_each really annoying to use even with lambdas:

 std::for_each(c.begin(), c.end(), [&](ExactTypeOfContainedValue& x) { // do stuff with x }); 

But using range-based is much better:

 for (auto& x : c) { // do stuff with x } 

On the other hand, if we are talking about C ++ 1y, then I would say no, the algorithms will not be outdated in range. The standard C ++ committee has a research group that is working on a proposal to add ranges to C ++, and is also working on polymorphic lambdas. Ranges eliminate the need for a pair of iterators, and polymorphic lambda allows you not to specify the exact argument type of a lambda. This means that std::for_each can be used like this (do not consider this a difficult fact, this is what dreams look like today):

 std::for_each(c.range(), [](x) { // do stuff with x }); 
+3
Jan 10 '13 at 13:40
source share

It should be noted that the algorithm expresses what is being done, not how.

A range-based cycle includes how everything is done: start from the first, apply and go to the next element to the end. Even a simple algorithm can do something different (at least some overloads for specific containers, without even thinking about a terrible vector), and at least the way it is done is not the business of the writer.

For me, most of the difference is, encapsulate as much as you can, and this justifies the suggestion, when you can, use algorithms.

+1
Jan 11 '13 at
source share

The range for the loop is simple. Until, of course, the standard is changed.

An algorithm is a function. A function that makes some demands on its parameters. The requirements are formulated in the standard in order, for example, to implement an implementation that uses all available execution threads and automatically speeds up the process.

+1
Jan 11 '13 at 22:26
source share



All Articles