Messing With Loops

I was recently involved in a Facebook discussion thread about for loops (which should give you some idea of the kinds of people I hang out with...), and someone stated that the condition and iterator were necessary components. This is, of course, false, and after swiftly correcting them I set out to see just how much the standard will let you get away with. Note that I'm using C++, although I suspect that C#/Java/etc. will have similar behaviours. All of this was tested using MSVC under Visual Studio 2015. Here, for your entertainment, is what I found.

The Classical Loop

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

This is the classical form of the for loop, and one which I'm sure every CS101 student has faced more times than they'd care to remember. Nice and simple.

The Oldschool Loop

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

If I'm remembering correctly, older versions of the C spec state that declarations are not allowed in the init section of the for statement. Thus the iterator variable is declared outside, and just initialized in the for statement.

The Infinite Loop

for (;;)

Because each section is optional, we can omit all three and get the above. When the condition section is omitted, the condition becomes a constant true, so the loop becomes non-terminating.

Caring Too Much About Performance

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

Because != and ++i might be faster than < and i++ respectively, but that's a topic for another article, another time.

Declaring Multiple Variables

for (int i = 0, j = 5; ...)

We can use comma-separation to declare multiple variables in the init section, so long as they are the same type.

Initializing Multiple Variables of Multiple Types

int i;
float j;
for (i = 0, j = 0.5f; ...)

So long as they're declared outside of the loop, we can initialize variables of mixed types.

Declaring Multiple Variables + Pointers

for (int i = 0, *j = &i, **k = &j; ...)

C has this really funky feature where, when declaring a variable, the pointer property is associated with the variable name rather than the type. The statement int* a, b; declares an int* called a and an int called b, rather than two int*s. The init section in a for loop follows the same wacky behaviour, giving us the lovely example above.

Increment all the things!

for (... i++, j++)

Just like the init section, the iteration section also allows comma-separation for multiple calls. Nothing too amazing about that.

Calling Functions

for (foo(); ...)
for (...; foo(); ...)
for (... foo(), bar())

We can call functions inline in any section, and they'll happily run. We can even call multiple functions using the comma-separation syntax. For the condition section, the function has to return a value that can be evaluated as boolean, unless...

Where Everything's Evaluated and the Results Don't Matter!

for (...; foo(), bar(), qux(), i < 20, i < 10; ...)

The condition section also allows for comma-separation, but here's where it gets interesting. Every expression in the condition section will be evaluated (from left to right), but only the right-most expression will actually be checked to see if the loop should continue to run. The other expressions don't have to evaluate to boolean values at all! In the example above, even though i is checked against 20, the loop will only run while i < 10.

Silence of the Lambdas

for (int i = 0; [&]{ return i < 10; }(); i++)

We can't create arbitrary scope in a for statement (so for (int i = 0; i < 10; { i++; }) is illegal), but we can get around this fairly trivially by abusing C++ lambdas. Create a lambda expression and invoke it inline, and now we can create arbitrary scope in any of the sections. Remember that the usual scope rules apply, so don't use this in the init section and then expect any variables declared inside it to be accessible from outside.


int i = 0;
for (std::function<bool()> cond = [&]{ return i < 10; }, iter = [&]{ i++; return true; }; cond(); iter())

Combining our knowledge of comma-separated declaration, function invocation, and lambdas, I give you this trainwreck. I'm sorry.