-6
int a = 0;
cout<<(a++, ++a, a++, ++a, a++)<<"\n";

I am doubting that the above code isn't undefined behavior, and I want to verify that.

I know the following code has UB:

int a = 0;
cout << a++ << " " << ++a << " " << a++ << "\n";
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Migo
  • 9
  • 2
  • 3
    The first is fine. The second is fine starting from C++17. Refer to https://en.cppreference.com/w/cpp/language/eval_order – HolyBlackCat Mar 06 '23 at 19:59
  • Since C++17 both aren't UB anymore, but they are unspecified, i.e. it's legal code, but the outcome is compiler-dependent. See https://stackoverflow.com/questions/55590378/c-sequence-points-and-changes-to-evaluation-order-in-c17 for more details. – chris_se Mar 06 '23 at 20:00
  • 1
    @chris_se Are you sure? The linked thread is about `f(a++, a++)`, in that case the `,` has a different meaning. – HolyBlackCat Mar 06 '23 at 20:09
  • @HolyBlackCat Oh, sorry, I thought I read a function call in the first example before the opening brace. You're right, with the comma operator it's perfectly valid and specified. (The second is still unspecified though, and the `f(a++, a++)` applies there.) – chris_se Mar 06 '23 at 20:12
  • 5
    From the amount of times these types of questions are asked, I guess these questions are what is deemed more important to teach beginners than to teach proper C++ coding techniques and usage. The real "undefined behavior" will be your programming team's reaction to you when they see code looking like this (it could be anything from laugh, to have you fired). – PaulMcKenzie Mar 06 '23 at 20:19
  • @chris_se No, the second is fine. Cppreference has almost the exact same wording for `,` and `<<`,`>>`. – HolyBlackCat Mar 06 '23 at 20:26
  • 1
    @Migo - These kind of quiestions are kind of boring, because there is no practical use for the code. 1, You should not mix computations and output, because that make the code hard to read. 2. Have you tried `a += 5;`? – BoP Mar 06 '23 at 20:33
  • 1
    When I first started to learn C a long time ago, I ran across this type of question one time, and told the behavior was undefined -- then that was it. Having new programmers ask multiple questions on different lines of code with various pre and post `++` all over the place -- that is a waste of time and resources that could have been spent learning other meaningful aspects of C++. – PaulMcKenzie Mar 06 '23 at 20:38
  • @PaulMcKenzie Maybe so. But the C++ standardisation committee obviously spend a non-trivial effort to evolve the standard (to produce C++17) in ways that makes some such examples have something other than undefined behaviour (some cases well-defined, others implementation-defined or unspecified). The impact on examples like this may have been intentional or not (I don't know). There's also the phenomenon that a lot of teachers and beginners to both C and C++ seem to have a fascination with these examples. – Peter Mar 07 '23 at 01:23

1 Answers1

5

The first example does not have undefined behaviour because it uses the built-in comma operator. The built-in comma operator guarantees that the left operand is sequenced before the right operand. That means that all value computations and side effects of the left operand occur before all value computations and side effects of the right operand. For example, both the read from a and the write to a that are performed by the first a++ occur before both the read from a and the write to a that are performed by the first ++a, and so on.

The second example has well-defined behavior in C++17 and later because in C++17 and later it is guaranteed that in a shift expression E1 << E2, the expression E1 is sequenced before E2. Note that in this case the << operator means a call to an overloaded operator<< function; however, a use of an operator that invokes an overloaded operator function still has the same sequencing rules as the built-in operator (see here; credit goes to HolyBlackCat for pointing this out).

But before C++17, the second example has undefined behavior because the rule that E1 is sequenced before E2 did not exist. To understand the behavior of the expression, we would need to write it out in terms of the actual calls to the overloaded operator functions:

operator<<((operator<<(cout.operator<<(a++), " ")).operator<<(a++), " ").operator<<(a++);

Here, there are no comma operators involved. The commas here only separate function arguments from each other but do not invoke the built-in comma operator.

Because C++ prior to C++17 offers no guarantees that the expression denoting a function to be called is evaluated before its arguments, the three a++s are unsequenced. These unsequenced modifications have undefined behavior.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
  • The explanation for `<<` isn't right. [This](http://eel.is/c++draft/expr#call-note-8) says that `A @ B` doesn't have the same eval order as `A.operator@(B)`. Rather, the sequencing of `A << B` is hardcoded in [`[expr.shift]/4`](http://eel.is/c++draft/expr#shift-4) – HolyBlackCat Mar 07 '23 at 21:15
  • @HolyBlackCat Good catch. I'll edit the answer. – Brian Bi Mar 07 '23 at 21:39