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
// ...
}
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 ) {
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 ) {
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 ) {
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 ) {
Of course, you can name
loop
anything you want;iter
orit
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 ) {
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)
Noice!