39

More often than not we need loops like this

do
{
     Type value(GetCurrentValue());
     Process(value);
}while(condition(value));

Unfortunately this will not compile, because value's scope ends at }. Which means that I will have to declare it outside the loop.

Type value;
do
{
    value = GetCurrentValue(); 
    Process(value);
}while(condition(value));

I don't like this for at least two reasons. For one, I like declaring things locally. And second, this is a problem if value is not assignable or default-constructible, but only copy-constructible.

So, my question has two sides. First, I'd like to know if there was a particular reason/difficulty in extending the do while's scope to the final condition as well (just as the scope of variables declared in for loop includes the body of the for loop despite it physically being outside of the braces). And if you believe that the answer to my first question is "It's just the way it is. Don't ask why questions." then I'd like to know if there are idioms that can help write do-while loops similar to the ones in my example but without the downsides I mentioned.

Hope the questions are clear.

Armen Tsirunyan
  • 130,161
  • 59
  • 324
  • 434
  • 3
    You realize this is the same for every other kind of loop, right? This is not specific to do/while, so you're really asking for do/while to be a special case. – Ed S. Nov 08 '12 at 20:29
  • 1
    You could potentially clean this up while retaining the scope of the condition variable by using a `for` loop instead. – John Dibling Nov 08 '12 at 20:30
  • 1
    *More often than not we **need** loops like this* --> I have not used a *do-while* loop in quite a few years. – David Rodríguez - dribeas Nov 08 '12 at 20:37
  • 1
    Not the same thing. You're talking about using a variable declared in the *body* of the loop. Your example above is declaring and assigning a variable in the check itself, which wouldn't make much sense in a do/while as the check does not occur until after the first iteration. – Ed S. Nov 08 '12 at 20:37
  • @EdS. I realized that and removed my comment. Seems like my question makes little sense. I'd gladly remove it but I can't because of the answer :) – Armen Tsirunyan Nov 08 '12 at 20:38
  • 1
    I think the pattern I'd go for would be a for loop with an inital setting of 'value'. so tour loop becomes... `for (Type value = trueCondition; condition(value);) { value = GetCurrentValue(); Process(value); }` – Roddy Nov 08 '12 at 20:49

2 Answers2

24

If you'd like to keep value locally scoped for the while loop, you can do this instead:

do
{
     Type value(GetCurrentValue());
     Process(value);
     if (! condition(value) )
         break;
} while(true);

This is just personal preference, but I find while loops structured like the following more readable (while instead of do-while):

while(true) {
    Type value(GetCurrentValue());
    Process(value);
    if (! condition(value) ) {
        break;
    }
}

The scoping rules in C/C++ works as follows: Local variables declared within a brace {...} block is local / visible only to that block. For example:

int a = 1;
int b = 2; 
{
    int c = 3;
}
std::cout << a;
std::cout << b;
std::cout << c;

will complain about c being undeclared.

As for rationale - it's just a matter of consistency and "that's just how the language is defined"

sampson-chen
  • 45,805
  • 12
  • 84
  • 81
  • 1
    Has nothing to do with the question. The question is "why", not "how to work around it". – Ed S. Nov 08 '12 at 20:30
  • 18
    @EdS., John: OP said 'And if you believe that the answer to my first question is "It's just the way it is. Don't ask why questions." **then I'd like to know if there are idioms that can help write do-while loops similar to the ones in my example but without the downsides I mentioned.**' – kennytm Nov 08 '12 at 20:33
  • 1
    And it's still helping people and adding value even today...one more upvote added. – Gabriel Staples Apr 24 '19 at 02:07
  • 3
    I interpret the question as "There is a special case for `for` loops, allowing a variable to be declared where it is initialized (in the control parentheses), but still acting as though it is declared in the scope of the body. Why is there not a similar special case for `while` loops, allowing variables declared in the body to be visible in the control parentheses?" (I interpret it this way because it's what I'd like to know :-) – John H. Aug 05 '20 at 18:28
  • The answer below answers my question above. – John H. Dec 07 '22 at 21:32
5

There is a good reason why variables declared inside the do-while loop body are out of scope in its condition expression: reduce the possibilities to deal with undefined behavior due to uninitialized variables.

Consider a slight variation of your example snippet:

do {
     if (not_ready_yet()) {
         sleep(1);
         continue;
     }
     Type value(GetCurrentValue());
     Process(value);
} while (condition(value)); // error

If C++ would allow a loop-scoped variable to be used in the loop condition, such a jump (via continue) to the condition expression would yield undefined behavior because it accesses an uninitialized variable (value in our example).

Having it the way it is, such errors can't be made.


Since C++ allows for many ways to use uninitialized variables, such as

for (int j; j<10; ++j)
    do_something();

or

int foo(int i) {
    if (i > 10)
        goto end;
    int x = 23;
end:
    return x;
}

or just

int foo(int i)
{
    int k;
    return k + i + 1;
}

the above reason might not be what drove the designers of the do-while loop, in the first place.


Having it the current way simplifies the compiler (and language), because a compound statement that is a do-while loop body has the exact same scoping rules as all other compound statements.

This was perhaps a strong argument for early C compilers that had to deal with limited resources. And since C++ was built on C, there is a strong incentive to not change such elementary design decisions.


Looking at theoretical alternatives, besides changing scope-rules just for do-while, something like this could be an option:

do (Type value(GetCurrentValue())) {
     Process(value);
} while (condition(value));

However, this might confuse people on whether or not value is re-initialized each iteration.


With the current language, re-writing it like this isn't too bad, though:

for (;;) {
     Type value(GetCurrentValue());
     Process(value);
     if (!condition(value))
         break;
}

Just one more line than in your original snippet. And less to type than:

do {
     Type value(GetCurrentValue());
     Process(value);
     if (!condition(value))
         break;
} while (true);
maxschlepzig
  • 35,645
  • 14
  • 145
  • 182