while (auto x=y; z)
source link: https://quuxplusone.github.io/blog/2020/10/28/while-with-initializer/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
This question has come up at least twice on the cpplang Slack now. C++ keeps
adding more and more knobs to if
and for
and switch
; why hasn’t it
messed with while
? Specifically, why isn’t there a “two-part while loop”?
C++ offers the following control structures (where “init
” represents
the choice of either a declaration or an expression-statement):
if (z) // '98
if (auto z=w) // '98
if (init; z) // C++17
if (init; auto z=w) // C++17
if constexpr (z) // all C++17
if constexpr (auto z=w)
if constexpr (init; z)
if constexpr (init; auto z=w)
for (init; z; ++x) // '98
for (init; auto z=w; ++x) // '98
for (auto e : r) // C++11
for (init; auto e : r) // C++20
while (z) // '98
while (auto z=w) // '98
do { ... } while (z) // '98
switch (z) // '98
switch (auto z=w) // '98
switch (init; z) // C++17
switch (init; auto z=w) // C++17
Notably missing from the middle of this list: while (init; z)
and
while (init; auto z=w)
.
The reason while (init; cond)
is missing is that there are two
reasonable interpretations of what it might mean. The Committee could
pick one behavior to be “correct”; but if they did that, programmers
would inevitably write the construct expecting the other behavior,
and then they’d have bugs.
Option 1: Evaluate the init only once
Kirit Sælensminde offered the following use-case:
auto cursor = getCursor();
while (auto item = cursor.next()) {
use(item);
}
If this is your use-case, then it might seem unfortunate that you can’t
combine the declaration of cursor
into the while
-loop; it has to
“leak” into the outer scope. You might want to write
while (auto c = getCursor(); auto item = c.next()) {
use(item);
}
It turns out that you can actually write this loop in a way
that’s just as short, and arguably clearer (since it uses only
C++98 features): just use a for
loop instead!
for (auto c = getCursor(); auto item = c.next(); ) {
use(item);
}
Option 2: Evaluate the init every time
The other use-case for which you might want a two-part while
loop is:
int ch;
while ((ch = getchar()) != EOF) {
use(ch);
}
If this is your use-case, then it might seem unfortunate that you can’t
combine the declaration of ch
into the while
-loop; it has to
“leak” into the outer scope. (And go uninitialized, too!) You might want to write
while (int ch = getchar(); ch != EOF) {
use(ch);
}
I’m not aware of any clean way to write this loop that avoids leaking
ch
into the outer scope. I mean, I don’t consider any of these “clean”:
for (int ch; (ch = getchar()) != EOF; ) {
use(ch);
}
for (int ch = getchar(); ch != EOF; ch = getchar()) {
use(ch);
}
while (true) {
if (int ch = getchar(); ch != EOF) { // C++17
use(ch);
} else {
break;
}
}
So, given that there’s a clean way to write Option 1 already, and no equally clean way to write Option 2, shouldn’t the Committee simply add Option 2 to the language? No.
We can’t give programmers the ability to write
while (int ch = getchar(); ch != EOF) { ... }
without also giving them the ability to shoot themselves in the foot with
while (auto c = getCursor(); auto item = c.next()) {
// repeatedly fetch the list and process just its first item,
// over and over, forever
}
I’m happy to keep programming without a complicated “two-part while
loop,”
if it means that other programmers are happily prevented from shooting themselves
in the foot.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK