Why no "while statement with initializer" ?

4,005 views
Skip to first unread message

Johannes Schaub

unread,
Oct 8, 2016, 1:05:02 PM10/8/16
to std-dis...@isocpp.org
Hello all,

in C++17 we will most probably get "if(init; cond)" and "switch(init;
cond)". Currently in C++14 it is already possible to say "while(decl)"
such as "while(char c = stream.peek()) ...". Why do we not extend this
aswell to support the "decl and cond" syntax? For example to say

while(char c = stream.peek(); c != ' ') { ... }

In the proposal I could not find a reason for this. I asked about this
on stackoverflow, but the people there do seem to think it's the most
natural thing that this will never work for "while"
(https://stackoverflow.com/questions/39933053/why-is-declaration-then-condition-in-c17-not-allowed-on-while).
Thanks everyone!

Nicol Bolas

unread,
Oct 8, 2016, 2:10:56 PM10/8/16
to ISO C++ Standard - Discussion
Maybe it's because people do not find your reasoning compelling that `while(init; cond)` should behave differently from `for(init; cond;)`.

Your logic is based on the idea that `while(cond-that-has-init)` behaves differently from `for(init; cond;)`, and therefore `while(init; cond)` should naturally behave differently from `for(init; cond;)`. That however ignores the fact that `init; cond` are two satements, while `cond-that-has-init` is a single expression.

Or to put it another way, `while(cond-that-has-init)` is equivalent to `for(;cond-that-has-init;)`, not to `for(init; cond;)`. As such, `while(init; cond)` should conceptually be equivalent to `for(init, cond;)`. And people would be surprised if it was not.

What you are asking for is a very different thing from `while` and `for`: an "initialization" statement that is executed once per loop before the condition. The closest we have to that is the way range-based `for` begins each loop iteration by initializing a variable by accessing an iterator. And that gets executed after the condition, rather than before.

Victor Dyachenko

unread,
Oct 10, 2016, 3:09:59 AM10/10/16
to ISO C++ Standard - Discussion
I find this odd too!
Without this feature we have to write

for(;;)
{
   
char c = stream.peek();
   
if(c == ' ') break;
    loop body
...
}

T. C.

unread,
Oct 10, 2016, 3:12:51 AM10/10/16
to ISO C++ Standard - Discussion
for(char c; (c = stream.peek()) != ' '; )
{
}

Ville Voutilainen

unread,
Oct 10, 2016, 3:16:46 AM10/10/16
to std-dis...@isocpp.org
Or alternatively,

for (char c = stream.peek(); c != ' '; c = stream.peek)

The answer to people who want an initializer in a while statement is
"use a for-loop instead".

Victor Dyachenko

unread,
Oct 10, 2016, 3:31:26 AM10/10/16
to ISO C++ Standard - Discussion
On Monday, October 10, 2016 at 10:12:51 AM UTC+3, T. C. wrote:
for(char c; (c = stream.peek()) != ' '; )
{
}

It is not more readable and contains local variable declaration w/o initialization. Not big deal, but still the element of bad style.
 
for (char c = stream.peek(); c != ' '; c = stream.peek)
Code duplication here. And even in this tiny snippet you mistyped the second expression ;-)
 

Ville Voutilainen

unread,
Oct 10, 2016, 3:37:32 AM10/10/16
to std-dis...@isocpp.org
On 10 October 2016 at 10:31, Victor Dyachenko
Neither of which are sufficient rationale for changing the language
for a while loop.

Domen Vrankar

unread,
Oct 10, 2016, 3:41:43 AM10/10/16
to std-dis...@isocpp.org
The initializer statement is executed only once so the while version with initializer would be written as:

while(char c = stream.peek(); c != ' ') {
   /* some code that handles c */

   c = stream.peak();
}

This also duplicates code as the second version of for and it also has the reading part split so it's still worse than the for alternative. Doesn't make much sense to add it if it somewhat solves something that is already solved better in for loop.

Regards,
Domen

Johannes Schaub

unread,
Oct 10, 2016, 3:49:56 AM10/10/16
to std-dis...@isocpp.org

In the era of auto and deduced types, this code is just ugly. And the weird nested assinment is another gross thing I hoped we could abandon

Domen Vrankar

unread,
Oct 10, 2016, 5:16:35 AM10/10/16
to std-dis...@isocpp.org
Forgot to mention that what you probably want (and expect that the provided while snippet does)  is to convert:

char c;
while(c = stream.peek(), c != ' ')

which is already an option with current standard into:

while(char c; c = stream.peek(), c != ' ')

which uses comma (quite often people don't like the use of comma operator...) and looks like for would be a better choice.

Regards,
Domen

Johannes Schaub

unread,
Oct 10, 2016, 5:45:32 AM10/10/16
to std-dis...@isocpp.org

The feature under discussion will reevaluate the declaration aswell. Otherwise there would be no point in it at all compared to a for loop.

In my view, a while loop evaluates its condition everytime it finishes a revolution. If you extend the syntax of the condition, I don't understand why this simple principle should be changed in favour of syntactic similarities with a for loop, which is a different construct.

From the discussion with multiple people, I have seen that there are multiple "interpretations" of the feature on "if": one is that there is an init statement and a condition (the view of most people that participated so far in discussion) and the other interpretation is that the existing "declaration in if" syntax was extended to allow changing the default "if the name evaluates to true, then ..." by specifying a followup expression.

The syntax used in the Standard supports the first interpretation, however.

I take it from the discussion that the actual reason is "no one proposed it, and we think that the similarities but differences with the for statement would be confusing"

Domen Vrankar

unread,
Oct 10, 2016, 6:35:13 AM10/10/16
to std-dis...@isocpp.org

The feature under discussion will reevaluate the declaration aswell. Otherwise there would be no point in it at all compared to a for loop.

Missed that part... I got confused by the comparison to switch and if. Sorry for that.

From the discussion with multiple people, I have seen that there are multiple "interpretations" of the feature on "if": one is that there is an init statement and a condition (the view of most people that participated so far in discussion) and the other interpretation is that the existing "declaration in if" syntax was extended to allow changing the default "if the name evaluates to true, then ..." by specifying a followup expression.

The syntax used in the Standard supports the first interpretation, however.

The reason is probably that with init and condition you can (I'm guessing that that would be allowed) write something like this:

if(auto a = get_possible(), auto b = get_always(); a != null && b == 'x') {}
else if(b == 'y') {}
else {} 

I take it from the discussion that the actual reason is "no one proposed it, and we think that the similarities but differences with the for statement would be confusing"

It would confuse me somewhat since the if and switch are already defined for if and switch and also it wouldn't be useful for cases where you have a string and you want to reuse it:

std::string s;
while(read(s); s != "abc") {...}

as you'd like to reuse already allocated space and the syntax doesn't permit you to initialize the string variable in scope so once again I'd fall back to for loop.
Regards,
Domen

Domen Vrankar

unread,
Oct 10, 2016, 9:01:49 AM10/10/16
to std-dis...@isocpp.org
2016-10-10 12:34 GMT+02:00 Domen Vrankar <domen....@gmail.com>:

From the discussion with multiple people, I have seen that there are multiple "interpretations" of the feature on "if": one is that there is an init statement and a condition (the view of most people that participated so far in discussion) and the other interpretation is that the existing "declaration in if" syntax was extended to allow changing the default "if the name evaluates to true, then ..." by specifying a followup expression.

The syntax used in the Standard supports the first interpretation, however.

The reason is probably that with init and condition you can (I'm guessing that that would be allowed) write something like this:

if(auto a = get_possible(), auto b = get_always(); a != null && b == 'x') {}
else if(b == 'y') {}
else {} 

Ups poor example.  Was in a hurry...

What I meant was:
if(auto a = get_possible(), auto b = get_always(); !a.has_value() || b == 0) {} /* a is optional (or could be a pointer) and b is 0 (so false)*/
else if(b == 2) {} /* a is definitely set */
else {} /* a is definitely set here as well */

So the init part is not condition that for e.g. optional or pointer must be set or value must evaluate to true in order for that branch to be chosen.
Not sure what you meant by "if the name evaluates to true, then ..." but if you meant "if the value ..." then the above example shows why that is not always what you'd wish for. If you meant the case with optional - ideas of optional<type_a> converting to type_a  automatically in the init part of if statement (pattern matching logic) - I'm not certain how that reasoning is related to the while example you've provided.

Regards,
Domen

Matthew Woehlke

unread,
Oct 12, 2016, 10:35:14 AM10/12/16
to std-dis...@isocpp.org
On 2016-10-10 03:37, Ville Voutilainen wrote:
> On 10 October 2016 at 10:31, Victor Dyachenko wrote:
>> On Monday, October 10, 2016 at 10:12:51 AM UTC+3, T. C. wrote:
>>> for(char c; (c = stream.peek()) != ' '; )
>>> {
>>> }
>>
>> It is not more readable and contains local variable declaration w/o
>> initialization. Not big deal, but still the element of bad style.
>>
>>> for (char c = stream.peek(); c != ' '; c = stream.peek)
>>
>> Code duplication here. And even in this tiny snippet you mistyped the second
>> expression ;-)
>
> Neither of which are sufficient rationale for changing the language
> for a while loop.

Replace 'char' with something that has a non-trivial default ctor. Or
worse, is not assignable. And replace 'stream.peek()' with something
more complicated. (Keep in mind that one reason duplication is bad is
due to the increased chance of the two statements getting out of sync.)

That said:

for ever // #define ever (;;)
if (init; cond)
{
...loop body...
}
else break;

--
Matthew

codew...@gmail.com

unread,
Jan 25, 2019, 12:40:22 AM1/25/19
to ISO C++ Standard - Discussion
Funny thing.  I just wanted to do this.

std::weak_ptr<T> p;
...
...

while (std::shared_ptr<T> q = p; q!=null_ptr)
{
  //do stuff with q
}

or something along these lines.  I want q to be destroyed and reconstructed each pass through the loop, because I want the chance for the object to be destructed, if nobody _else_ is holding on to the object referenced by p.  

I knew roughly about the if-with-initialization syntax, and so this was a 'natural' offshoot of that, and I was mildly surprised that it didn't exist.

This differs from a for loop which does its initialization only once, giving it a purpose, but mimics the if-with-initialization version where the creation and scope of the variable is only within the loop, which is the only place I need it.  I don't need q outside the loop.

Putting the initialization above the loop gives it a broader scope than I intend.  Further, semantically, its not clear that if I do this:

std::shared_ptr<T> q;
while ((q = p) !=null_ptr)
{
  //do stuff with q
}

that at the point of re-assigning q on multiple passes through the loop that for a brief moment, the count decreases and then increases.  So unless I could be guaranteed of that, its not semantically what I want either.

Yes, piles of workarounds, obviously, and I'm not arguing that this is a really big use case.

I suppose all I'm arguing is the word 'natural' that's cropped up in this thread.  This seemed natural to me.  At least at the time.

Peter Sommerlad

unread,
Jan 25, 2019, 2:59:06 AM1/25/19
to std-dis...@isocpp.org
That is what is called a for loop....

for (auto q = p.lock(); q ; )
{
  //do stuff with q
}

Sent from Peter Sommerlad's iPad
--

---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussio...@isocpp.org.
To post to this group, send email to std-dis...@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.

Nicol Bolas

unread,
Jan 25, 2019, 10:08:49 AM1/25/19
to ISO C++ Standard - Discussion
On Friday, January 25, 2019 at 2:59:06 AM UTC-5, PeterSommerlad wrote:
That is what is called a for loop....

No, it isn't. He specifically said "I want q to be destroyed and reconstructed each pass through the loop". A `for` loop won't do that.

Now, there are two questions around this: does this use case (wanting to re-execute the initializer every time through the loop) come up often enough to be worth the syntax? And will this use case be confusing, because users will think that the `while` loop will behave like the `for` equivalent (which is exactly what Peter thought when he saw the example)?

Personally, I think the answers to these questions (no and yes, respectively) disqualify this idea as a proposal for a C++ language change.

tko...@google.com

unread,
Jan 25, 2019, 11:34:50 AM1/25/19
to ISO C++ Standard - Discussion, codew...@gmail.com
On Friday, 25 January 2019 05:40:22 UTC, codew...@gmail.com wrote:
Funny thing.  I just wanted to do this.

std::weak_ptr<T> p;
...
...

while (std::shared_ptr<T> q = p; q!=null_ptr)
{
  //do stuff with q
}

or something along these lines.  I want q to be destroyed and reconstructed each pass through the loop, because I want the chance for the object to be destructed, if nobody _else_ is holding on to the object referenced by p.  

I knew roughly about the if-with-initialization syntax, and so this was a 'natural' offshoot of that, and I was mildly surprised that it didn't exist.

How about:

std::weak_ptr<T> p; 
while (std::shared_ptr<T> q = p.lock()) {
  // do stuff with q
}

codew...@gmail.com

unread,
Jan 25, 2019, 12:28:01 PM1/25/19
to ISO C++ Standard - Discussion
Nope.  Wrong semantics for what I wrote.  For loop doesn't reconstruct q each time.  I specifically addressed that in my post.

codew...@gmail.com

unread,
Jan 25, 2019, 12:32:23 PM1/25/19
to ISO C++ Standard - Discussion
Oh, I actually agree its confusion inducing probably disqualifies it outright.  So no argument from me.  But I felt I had to at least offer where  I'd run into a brief "natural" want.
Message has been deleted
Message has been deleted

Matthew Woehlke

unread,
Jan 28, 2019, 11:20:16 AM1/28/19
to std-dis...@isocpp.org
On 25/01/2019 00.40, codew...@gmail.com wrote:
> Funny thing. I just wanted to do this.
>
> std::weak_ptr<T> p;
> ...
> ...
>
> while (std::shared_ptr<T> q = p; q!=null_ptr)
> {
> //do stuff with q
> }
>
> or something along these lines. I want q to be destroyed and reconstructed
> each pass through the loop, because I want the chance for the object to be
> destructed, if nobody _else_ is holding on to the object referenced by p.

Weeeeeeelll...

for (;;) if (std::shared_ptr<T> q = p; q!=null_ptr)
{
// do stuff with q
} else break;

tkoeppe's answer is probably better *for your case*, but my version is
equivalent to yours for any declaration and conditional-expression.

Of course, you can also just write it:

for (;;)
{
std::shared_ptr<T> q = p;
if (!q) break;
// do stuff with q
}

Hmm...

#define my_while(decl, expr) \
for (;;) if (decl; !(expr)) break else

...there you go ;-).

--
Matthew
Reply all
Reply to author
Forward
0 new messages