Previous section   Next section

Imperfect C++ Practical Solutions for Real-Life Programming
By Matthew Wilson
Table of Contents
Chapter 17.  Syntax


17.3. for

17.3.1 Initializer Scope

The rule-change for the for expression that was enacted in C++-98 is a porter's nightmare. Basically, the old rule was that a variable declaration in the initializer statement of a for statement would exist in the scope in which the for existed. Hence the code in Listing 17.4 is legal.

Listing 17.4.


for(int i = 0; i < 10; ++i)


{


  . . .


}





i = 0; // i still exists here





for(i = 0; i < 10; ++i) // Re-use i here by old rule


{


  . . .


}



The rule change was to place the initialiser statement variables within the scope of the for statement, as in Listing 17.5.

Listing 17.5.



for(int i = 0; i < 10; ++i)


{


  . . . // i is valid here


}





i = 0; // but does not exist here. Compile error





for(int i = 0; i < 10; ++i) // A different i by the new rule


{


  . . .


}



This rule change was to tighten up the scoping of the initializer variables, since it is possible under the old rule for variables declared in the initializer statement to be reused elsewhere within the containing scope, breaking locality of scope, and leading to unintended side effects and maintenance headaches.

Obviously these two rules can be completely contradictory for much reasonable existing code. Trying to maintain a simple code-base for various compilers is a challenge.

Imperfection: The contradiction between the old and new for-statement rules results in nonportable code.


Since portability (even so far as between versions of the same compiler) is a highly desirable attribute of software, we need a form that works with both old and new rules. One approach ([Dewh2003]) is to use a second enclosing scope, placing the entire for statement within a block:



{ for(int i = 0; i < 10; ++i)


{


  . . .


}}



This in effect forces any old-rule code to the new-rule way of doing things. Since the new rule is more sensible than the old in an absolute correctness sense, this technique has the beneficial side effect of preparing the code and its author for the new way of thinking.

The power of the little old } is quite something to behold, as we see throughout the book.

17.3.2 Heterogeneous Initializer Types

Unfortunately, that is not the only problem with the for statement. In fact, there is a far more irritating imperfection. The initializer statement allows for more than one variable to be declared and initialized, as in:



for(int i = 0, j = 10; i < j; ++i)


{


  . . .


}



However, one quickly runs aground of common sense when trying to do something such as



for(int i = 0, vector<int>::const_iterator b = v.begin(); i < 10 && b != v.end(); ++i, ++b)


{


  . . .


}



This is because only one type specifier is allowed in the initializer statement. I can't see why such multitype statements cannot be unambiguously parsed; although not being a compiler-writer, I concede there may be a sound objection. Whether there is or not, it is an inconvenience, and leads us straight back to the semantics of the old for-rule, since we must take all but one of the types out of the initializer statement, as in:



vector<int>::const_iterator b = v.begin();


for(int i = 0; i < 10 && b != v.end(); ++i, ++b)


{


  . . .


}



or:



int i = 0;


for(vector<int>::const_iterator b = v.begin(); i < 10 && b != v.end(); ++i, ++b)


{


  . . .


}



or:[6]

[6] In this case, b is default constructed before copy assignment. For nonpointer iterators, this may be inefficient.



int                         i;


vector<int>::const_iterator b;


for(i = 0, b = v.begin(); i < 10 && b != v.end(); ++i, ++b)


{


  . . .


}



Whichever form you choose, one or both of these variables will still exist outside the scope of the for statement, so we're pretty much back to square one.

Imperfection: for statements requiring initializers of two or more types render the new for-scoping rule irrelevant.


We saw with the simple for statement problem that adding an extra scope helped make old for-rule code compatible with the new. We will use it again here, this time to facilitate the intended semantics of the new for-rule for code for which the rule itself fail to do so. It's very simple, albeit perhaps not very beautiful:



{ int                         i;


  vector<int>::const_iterator b;


for(i = 0, b = v.begin(); i < 10 && b != v.end(); ++i, ++b)


{


  . . .


}} // i and b both cease to exist here.



If you think that this rule is contrived, consider the case where you need to iterate through two or more containers, of different types, manually rather than using std::for_each; without this technique you are forced to use b_v, b_l, b_m (or begin_vec, begin_lst, begin_map) instead of using your customary b (or begin, or whatever). It makes the code hard to write, read, and maintain.

Note that this is not just a flaw of C++; several other languages—C#, D—exhibit precisely the same flaw with their loop constructs (for, foreach).


      Previous section   Next section