Loops are among the basic constructs you find in nearly all programming languages. Of the available variations, for loops are the most common ones. The word for is so small that it is not affected by the tax on long keywords and variable names that programming languages seem to be subject of. Thus, you will find for as a keyword in most of them.
C++11 supports a multitude of styles to write for loops. Let us have a look at some of these possibilities for a simple example.
Test case
We have a simple class representing a rectangle of given size:
class Rectangle { public: Rectangle(double height, double width) : height_(height), width_(width) { } double area() const { return height_ * width_; } private: double height_; double width_; };
Our customer, an important global player in the paper industry, wants to cut rectangular sheets of paper out of a larger one. As a simple check for our sophisticated cutting machine control algorithm we want to ensure that the total size of the small sheets of paper does not exceed the size of the large one. Thus, we need a function with the signature
double total_area(std::vector<Rectangle> const & rectangles);
The question is how to implement this function best.
1. Manual for loop with indices
Our first guess is the following function:
double total_area(std::vector<Rectangle> const & rectangles) { double total_area = 0.0; std::size_t const number_of_rectangles = rectangles.size(); for (std::size_t index = 0; index < number_of_rectangles; ++index) { total_area += rectangles[index].area(); } return total_area; }
Here, we have used the for construct known from C and made sure that size() is only called once. Above version seems familiar to most C++ programmers.
2. Manual for loop with iterators
Iterators provide a unified way of accessing containers, so let us try another version of total_area():
double total_area(std::vector<Rectangle> const & rectangles) { double total_area = 0.0; auto end = rectangles.end(); for (auto it = rectangles.begin(); it != end; ++it) { total_area += it->area(); } return total_area; }
This should look familiar to most C++ developers, too. The very same code would work for other standard containers such as std::list or std::deque.
3. STL-based for_each
The iterator-based approach of the previous section is a pattern often encountered in C++ programs. Though simple, there are a few pitfalls to be avoided. For performance reasons, end() should only be called once and stored in some temporary variable. In addition, one should use the prefix variant of the increment operator to avoid an unused temporary copy of the iterator at some previous state when the postfix operator is used. Some iterators may not even implement the postfix version.
To avoid these pitfalls, the C++’s standard template library (STL) offers a generic algorithm in the form of std::for_each. With the constraints of C++03, its application is more than a bit cumbersome:
struct area_adder { area_adder(double & external_data) : total_area(external_data) { } void operator()(Rectangle const & rectangle) { total_area += rectangle.area(); } double & total_area; }; double total_area(std::vector<Rectangle> const & rectangles) { double total_area = 0.0; area_adder sum_areas(total_area); std::for_each(rectangles.begin(), rectangles.end(), sum_areas); return total_area; }
No wonder C++03 is not the beginner’s first choice.
4. STL-based for_each with lambda function
Fortunately, C++11 comes to the rescue by providing a more simple way to use std::for_each: lambda functions. Lambda functions are unnamed functions which may capture variables of the context they are embedded in. This can be exploited for our purpose:
double total_area(std::vector<Rectangle> const & rectangles) { double total_area = 0.0; std::for_each(rectangles.begin(), rectangles.end(), [&total_area](Rectangle const & rectangle) { total_area += rectangle.area(); }); return total_area; }
The lambda function captures total_area by reference and manipulates its value. This code resembles the manual for loop with iterators, but avoids the common pitfalls. On the downside, lambda functions are more difficult to read. Some programmers also tend to write incohesive functions because there is no need to think of an appropriate name…
5. Boost’s for_each template with lambda functions
The boost libraries provide a more refined version of library-based for-loops.
#include <boost/range/algorithm/for_each.hpp> double total_area(std::vector<Rectangle> const & rectangles) { double total_area = 0.0; boost::for_each(rectangles, [&total_area](Rectangle const & rectangle) { total_area += rectangle.area(); }); return total_area; }
In contrast to std::for_each, boost::for_each does not require you to fiddle with iterators. In situations where you do not want to iterate over all elements of a container, you have to rely on a range adapter (try boost::iterator_range) to do the job. For our test case, however, boost::for_each does a reasonably good job. Keep in mind, though, that it introduces a dependency to the boost libraries. This is not too big a drawback, since boost libraries are of high quality in general.
6. Macro-based for loops with BOOST_FOREACH
boost::for_each is about the prettiest a library-based version of a for loop can get. For a change, let us have a look at a macro-based way to write one:
#include <boost/foreach.hpp> double total_area(std::vector<Rectangle> const & rectangles) { double total_area = 0.0; BOOST_FOREACH(Rectangle const & rectangle, rectangles) { total_area += rectangle.area(); } return total_area; }
This looks like a promising way for write a loop. There are no lambdas and iterators are nowhere to be seen. Still, it is not one I would recommend. Personally, I dislike macros because the macro mechanism is a text replacement system which does not know about the rules of C++. Bad and confusing things can happen if macros are used or written incorrectly. For this reason, I simply do not trust macros. I trust BOOST_FOREACH a little more, because it is around and used for quite a while and, importantly, I have not written it myself. All in all, BOOST_FOREACH is probably safe to use, but I do not like its siblings. Sorry.
7. C++11’s range-based for loops
A new C++11 feature concludes our illustrious set of for loops:
double total_area(std::vector<Rectangle> const & rectangles) { double total_area = 0.0; for (auto const & rectangle : rectangles) { total_area += rectangle.area(); } return total_area; }
Above snippet shows range-based for loops in action. The colon separates the range, e.g., a container, on the right-hand side from the current element on the left-hand side, which you can access in the body of the loop. It is short, efficient, convenient and, best of all, a language feature.
Conclusion
In this article we have demonstrated a few possibilities to write a simple for loop. Use the old index-based version if you need the index for other things besides accessing your container. for loops with iterators are fine if you want to iterate over subranges and you have no handy range adapter. Use range-based for loops if you have a recent compiler. If your compiler does not support range-based for loops yet, you can use BOOST_FOREACH (if you trust it) or boost::for_each. If your compiler does not support lambda functions, the latter may become quite cumbersome.
Now you know how to write clean for loops. It is cleaner still to avoid explicit loops altogether, since many common applications of loops are already provided as algorithms by the STL or boost.
Hi,
Nice summary of various ways to write for loops!
Originally not my idea, but I think it’s worth to share:
The first example can be rewritten with the comma operator. It prevents that [end] is visible outside the for loop and still defines a variable end, but in the for loop argument block.
I don’t know why it’s not that common, but I find it more convenient to write it this way.
double total_area(std::vector const & rectangles)
{
double total_area = 0.0;
for (auto it = rectangles.begin(), end = rectangles.end(); it != end; ++it) {
total_area += it->area();
}
return total_area;
}
Cheers,
Andre
Nice article. I keep wondering why you didn’t mention std::accumulate here, it seems to capture the intention best, no? (The C++11 for each loop may admittedly be shorter and easier to read.)
Hi Markus!
The reason for omitting std::accumulate here is rather simple: I wanted this article to be all about for loops and I just used the present example for illustration. I agree that algorithms like std::accumulate are a great tool to highlight the intention of your code. In his article May the FORs leave you alone Markus applies boost::accumulate to remove those pesky loops altogether.
Cheers
Michael
This should be the perfect example for the good and old std::accumulate!!
You are right. See above comment and response for why I chose not to use std::accumulate here.