DEV Community

Paul J. Lucas
Paul J. Lucas

Posted on • Updated on

Declaring Multiple Variables in a “for” Loop Initialization Clause

Introduction

As you know, the syntax of the for statement in C and C++ is:

    for ( init-clause ; condition-expr ; iteration-expr )

where:

  • init-clause is used to initialize (prepare for) the start of the loop;
  • condition-expr is evaluated before each iteration: if zero, the loop exits;
  • iteration-expr is evaluated after each iteration.

All are optional; if condition-expr is omitted, it’s as if it were 1, hence:

for (;;) {  // forever
  // ...
}
Enter fullscreen mode Exit fullscreen mode

loops forever (presumably to be exited by one of break, return, goto, exit(), longjmp(), abort(), or in C++, throw).

C++ also has range-based for loops, but that’s a story for another time.

Originally, the init-clause could be only a statement or expression; starting in C99, it could alternatively be a declaration:

for ( unsigned i = 0; i < m; ++i ) {
Enter fullscreen mode Exit fullscreen mode

which is nice because it limits the scope of the variable(s) declared to the loop body. Multiple variables may also be declared:

for ( unsigned i = 0, j = 0; i < m && j < n; ++i, ++j ) {
Enter fullscreen mode Exit fullscreen mode

The iteration-expr may use the comma operator to evaluate more than one sub-expression. (Actually, any expression may use the comma operator to evaluate more than one sub-expression.)

But what if you want to declare more than one variable where the types are different? You can’t. Instead, you’re forced to declare variables of different types outside the loop:

size_t i = 0;
for ( node_t *p = list->head; p->data; ++i, p = p->next ) {
Enter fullscreen mode Exit fullscreen mode

Or so I thought. As I commented:

Even in a relatively small language like C, I’m still learning new techniques even though I’ve been using C for over 35 years.

It turns out you can declare more than one variable where the types are different in the init-clause.

The Trick

The trick is to use a local, anonymous struct:

for ( struct { size_t i; node_t *p; } loop = { 0, list->head };
      loop.p->data; ++loop.i, loop.p = loop.p->next ) {
Enter fullscreen mode Exit fullscreen mode

Of course, you can name loop anything you want; iter or it are reasonable alternatives.

Granted, it looks weird and it’s somewhat ugly to have to refer to loop — but you can do it. One place it’s likely more useful is if the loop is part of a macro where the ugliness would be hidden.

But just because you can do it, should you? That’s debatable. After all, it’s not so terrible that variables of different types are declared outside the loop. It’s a trade-off with how important it is that all the variables are limited to the scope of the loop. In C, this typically isn’t that important.

In C++, however, if at least one of the loop variables is of a type that has a destructor and it’s important that it runs as soon as the loop terminates, then that might justify the use of the struct. Of course C++ has structured bindings that can be used instead:

for ( auto [i, p] = std::make_tuple( 0, list->head );
      p->data; ++i, p = p->next ) {
Enter fullscreen mode Exit fullscreen mode

Conclusion

The struct trick certainly isn’t that significant, perhaps not even enough to justify the existence of this article. But I do think it’s clever and perhaps even useful in limited cases, so I thought it worth passing on since I’ve never encountered it in over 35 years of programming in C.

Top comments (1)

Collapse
 
pgradot profile image
Pierre Gradot

Noice!