Introduction
During C++ standardization, the definition of the for statement changed: if a for statement defines a variable, the scope of that variable now ends at the end of the for statement rather than at the end of the surrounding block. This change creates a new reason to prefer a for statement to a while statement in some contexts, namely when it is desirable to limit the scope of a temporary variable that controls a loop.
Most of the contexts in which we should prefer a for to a while are well known. However, Walter Brown, a C++ standards committee member from the Fermi National Accelerator Laboratory, recently pointed out to us an elegant context that we had not previously seen, and that we think deserves a wider audience.
The rest of this article will explain the background and illustrate the technique.
An Example
Suppose we want to compute the sum of the integers in the range [1, 10) that is, the integers greater than or equal to 1 and less than 10. We might do so this way:
int sum = 0; int i = 1; while (i < 10) { sum += i; ++i; }
Alternatively, we might do it this way:
int sum = 0; for (int i = 1; i < 10; ++i) sum += i;
Once upon a time, these two program fragments had exactly the same meaning. However, as part of standardizing C++, the meaning changed slightly. If we amend our first fragment:
int sum = 0; int i = 1; while (i < 10) { sum += i; ++i; } // prints 10 std::cout << i << std::endl;
we can print the value that i has after the loop completes. In contrast, the same technique fails for the other fragment:
int sum = 0; for (int i = 1; i < 10; ++i) sum += i; // should not compile std::cout << i << std::endl;
The modified version of our second fragment should not compile because the variable i is no longer in scope: its scope ends at the end of the for statement.
More generally, if we ignore the effects of the break and continue statements, which are beyond the scope of this article, a statement of the form:
for (statement-1 expression-1; expression-2) statement-2
used to mean the same as:
statement-1 while (expression-1) { statement-2 expression-2; }
In Standard C++, our for statement means the same as:
{ statement-1 while (expression-1) { statement-2 expression-2; } }
where the extra braces serve to bound the scope of any variable declared as part of statement-1.
Note, incidentally, that this example does not show a semicolon at the end of statement-1 or statement-2. The reason is that statements usually end with semicolons of their own, so it is not appropriate to insert an additional semicolon.
Which Kind of Statement Should We Use?
C and C++ programmers have long faced the question of whether to use a for or a while statement to control a loop. Our own rule of thumb has been to prefer a for when we want to associate a variable with the loop that should not continue to exist after the loop is finished. So, for example, we would prefer a for to a while for summing the elements in [0, 10), but we would prefer a while if we were not defining a variable as part of the loop.
Note that such variables do not have to be integers. For example, we might traverse a linked list this way:
for (Node* p = head; p; p = p->next) { // process a list element }
with the idea that we would not want the variable p to persist after the loop completes. We can modernize this example by using a standard-library class and defining an iterator instead of a pointer:
for (std::list::const_iterator it = my_list.begin(); it != my_list.end(); ++it) { // process a list element }
The Role of the Loop Variable
In all of our for-statement examples, there is a variable that fills the conceptual role of an iterator: it takes on a value that either is, or is associated with, each element of a sequence in turn. Indeed, in thinking about the programs we have written, we cannot recall ever having written a for statement that does not have such a variable associated with it. We have seen other peoples programs in which the statement-1 that begins the for statement does not define or initialize a variable, and in which it may even be empty, but we have never written such a for statement ourselves. For example, we strongly prefer:
while (*p != ' ') ++p;
to
for ( ; *p != ' '; ++p);
even though the latter is more succinct.
An Elegant Technique
Now that you know the background, we can show you the beautifully elegant technique that Walter Brown recently mentioned to us.
Lets look at our first program fragment again, and lets change it so that instead of summing a fixed sequence, it computes the sum of a sequence of integers that it reads from the standard input stream. Until recently, we would have solved that problem this way:
int sum = 0; int x; while (std::cin >> x) sum += x;
We have been writing input loops like this one as long as weve been using C++ more than 15 years. Imagine our surprise, then, when Walter showed us the following alternative:
int sum = 0; for (int x; std::cin >> x; ) sum += x;
Here, the statement-1 is int x; (including the semicolon), the expression-1 is std::cin >> x, and the expression-2 is empty.
This rewrite helps us in two ways. First, it causes the variable x to disappear after the loop finishes, thereby removing the risk that someone might use x by mistake later. Second, it makes clear that, like the variables that we defined in our earlier for loops, the variable x is associated in turn with each element of the sequence that we are manipulating.
Of course, this technique will work with other functions as well, such as getline.
Incidentally, we could also have written this last example as:
int sum = 0; for (int x; std::cin >> x; sum += x) ;
We prefer not to use this form for an important reason: it mixes the code that controls the loop with the body of the loop itself. In other words, we prefer to use the form:
for (statements that control the loop) the body of the loop
to make our intention as clear as possible.
Discussion
Every once in a while a technique comes along that is such a blatant improvement, and so obvious in retrospect, that it is hard to understand why it was not already widespread. It is hard to think of a better recent example of the fact that ideas that are obvious in retrospect are not always obvious beforehand. Thank you, Walter Brown, for pointing this technique out to us, and for giving us permission to publicize it.
Andrew Koenig is a member of the Large-Scale Programming Research Department at AT&Ts Shannon Laboratory, and the Project Editor of the C++ standards committee. A programmer for more than 30 years, 15 of them in C++, he has published more than 150 articles about C++ and speaks on the topic worldwide. He is the author of C Traps and Pitfalls and co-author of Ruminations on C++.
Barbara E. Moo is an independent consultant with 20 years experience in the software field. During her nearly 15 years at AT&T, she worked on one of the first commercial projects ever written in C++, managed the companys first C++ compiler project, and directed the development of AT&Ts award-winning WorldNet Internet service business. She is co-author of Ruminations on C++ and lectures worldwide.