3

Is it possible to set a value more than once in a single stream such as std::cout?

For example, the code below produces the following output.

1
0
1
1
1

This surprised me as I was expecting the output

1
0
1
0
0

As far as I can tell, when it is all concatenated in one line, the (x = false) is ignored and it just prints x.

#include "stdafx.h"
#include <iostream>

int main()
{
    bool x;
    std::cout << (x = true) << std::endl;
    std::cout << (x = false) << std::endl;

    std::cout << (x = true) << std::endl
        << (x = false) << std::endl;

    std::cout << x << std::endl;

    return 0;
}
Dan
  • 7,286
  • 6
  • 49
  • 114
  • 7
    When you concatenate them into a single expression, you get undefined behavior, because neither assignment is ordered before/after the other (oh, and it's not really related to streams either--doing the same without stream insertion would still give undefined behavior). – Jerry Coffin Aug 19 '18 at 19:01
  • @JerryCoffin Thanks for the info. – Dan Aug 19 '18 at 19:09
  • 3
    @JerryCoffin Isn't it well-defined since C++17? – HolyBlackCat Aug 19 '18 at 20:01
  • 1
    @HolyBlackCat: I think there's some intent that it be the case, but I haven't been able to track down normative language that I'm sure really says so. §[expr.call]/5 has a note that says it should be, and refers to [over.match.oper], but that contains information about name lookup and operand conversions, not about sequencing. – Jerry Coffin Aug 19 '18 at 21:33
  • 1
    @JerryCoffin [see here](https://stackoverflow.com/a/50361417/1505939) for C++17 references – M.M Aug 20 '18 at 05:58

3 Answers3

3

Since C++17, the correct output for the code is 1 0 1 0 0 as you expected.

Prior to C++17, the behaviour was undefined. The C++17 standard introduced left-to-right sequencing for operands of <<.

If the compiler doesn't give the expected output in C++17 mode (-std=c++17) then it is a compiler bug. It has been previously noted on similar questions that the latest versions of g++ and clang++ give bogus warnings, and even in some cases incorrect behaviour in this area.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • @SkepticalEmpiricist the assignment expressions are operands of `<<` – M.M Aug 20 '18 at 07:15
  • Nice going with these SO links. Had no idea of the bugs in clang. Working my way through the spec as we speak and will update to reflect. Need to start second guessing clang and g++ more often. Thanks a lot, I definitely upvoted you on this : ) – Geezer Aug 20 '18 at 09:04
  • Thanks for the information. After setting my compiler to the C++ 17 standard it worked as expected – Dan Aug 20 '18 at 11:00
  • @Dan, you're now getting the expected result in runtime, but compilation does give out a warning, right? – Geezer Aug 20 '18 at 12:45
  • 1
    @SkepticalEmpiricist Using Visual Studio 2017, it does not seem to give any warnings from the compiler. I am not sure which compiler Visual Studio uses though – Dan Aug 20 '18 at 13:24
  • @Dan it uses 'msvc'. Goos to know it's being conformant on this one. Appreciate the feedback. – Geezer Aug 20 '18 at 14:00
2

Short answer: You didn't specify compiler/flags, so I'd encourage you to make sure you're building for C++17 (clang/g++ flag -std=c++17). This should fix your result, though still might cause a compilation warning. The how and why is below. Update: Unlike clang and gcc, msvc seem to be fully conformant on this one -- gives the expected result, warning-free.

Explanation: Unintuitively indeed, this unexpected result has nothing to do specifically with stream operations. It has to do with unsequenced evaluations of operands. See here in [intro.execution]:

Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced. [...] The value computations of the operands of an operator are sequenced before the value computation of the result of the operator. If a side effect on a memory location is unsequenced relative to either another side effect on the same memory location or a value computation using the value of any object in the same memory location, [...] the behavior is undefined.

...

i = i++ + i; // the behavior is undefined

But behold, << operator (shift operators) is being noted specifically to be sequenced in the current spec (since C++17) -- see here in [expr.call]:

If an operator function is invoked using operator notation, argument evaluation is sequenced as specified for the built-in operator;

combined with this in [expr.shift]:

The value of E1 >> E2 ... The expression E1 is sequenced before the expression E2.

For your snippet, clang 6.0.1 gives this informative warning that according to these excerpts from the spec is not in conformance with the standard (!):

warning: multiple unsequenced modifications to 'x' [-Wunsequenced] std::cout << (x = true) << std::endl

You can see it here live.

Note: This answer has been edited to reflect current state of things, thanks to user M.M who brought forth this enlightening SO link which refers to the appropriate bug reports on both clang and GCC.

Geezer
  • 5,600
  • 18
  • 31
  • C++17 introduced a sequencing order for `<<` (but not for `+`) – M.M Aug 20 '18 at 05:59
  • @M.M Ok wow bugs in clang I see. Will look in the spec and edit my answer accordingly. Thanks for noticing and thanks for the contribution. – Geezer Aug 20 '18 at 09:02
  • @M.M Thanks again. I've updated my answer and would love to hear if you think there's anything missing this time. – Geezer Aug 20 '18 at 10:49
0

The misunderstanding comes from this line:

std::cout << (x = true) << std::endl
    << (x = false) << std::endl;

Before anything is printed, the two subexpressions are evaluated in an undefined order. The all assign something to x, but the last assignment "wins" and writes the final value (in this case true) to x. Then x gets printed three times.

Matthias247
  • 9,836
  • 1
  • 20
  • 29