# With C++11, is it undefined behavior to write f(x++), g(x++)?

I was reading this question:

Undefined behavior and sequence points

and, specifically, the C++11 answer, and I understand the idea of "sequencing" of evaluations. But - is there sufficient sequencing when I write:

f(x++), g(x++); ?

That is, am I guaranteed that f() gets the original value of x and g() gets a once-incremented x?

Notes for nitpickers:

- Assume that operator++() has defined behavior (even if we've overriden it) and so do f() and g(), that no exceptions will be thrown, etc. - this question is not about that.
- Assume that operator,() has not been overloaded.

## Answers

No, the behavior is defined. To quote C++11 (n3337) [expr.comma/1]:

A pair of expressions separated by a comma is evaluated left-to-right;
the left expression is a discarded-value expression (Clause [expr]).
**Every value computation and side effect associated with the left
expression is sequenced before every value computation and side effect
associated with the right expression.**

And I take "every" to mean "every"1. The evaluation of the second x++ cannot happen before the call sequence to f is completed and f returns.2

1 Destructor calls aren't associated with sub-expressions, only with full expressions. So you'll see those executed in reverse order to temporary object creation at the end of the full expression. 2 This paragraph only applies to the comma when used as an operator. When the comma has a special meaning (such when designating a function call argument sequence) this does not apply.

##### No, it isn't undefined behavior.

According to this evaluation order and sequencing reference the left hand side of the comma is fully evaluated before the right hand side (see rule 9):

9) Every value computation and side effect of the first (left) argument of the built-in comma operator , is sequenced before every value computation and side effect of the second (right) argument.

That means an expression like f(x++), g(x++) is *not* undefined.

Note that this is only valid for the *built-in* comma operator.

# It depends.

First, let's assume that x++ by itself does not invoke undefined behavior. Think about signed overflow, incrementing a past-the-end-pointer, or the postfix-increment-operator might be user-defined). Further, let's assume that invoking f() and g() with their arguments and destroying the temporaries does not invoke undefined behavior. That are quite a lot of assumptions, but if they are broken the answer is trivial.

Now, if the comma is the built-in comma-operator, the comma in a braced-init-list, or the comma in a mem-initializer-list, the left and right side are sequenced either before or after each other (and you know which), so don't interfere, making the behavior well-defined.

struct X { int f, g; explicit X(int x) : f(x++), g(x++) {} }; // Demonstrate that the order depends on member-order, not initializer-order: struct Y { int g, f; explicit Y(int x) : f(x++), g(x++) {} }; int y[] = { f(x++), g(x++) };

Otherwise, if x++ invokes a user-defined operator-overload for postfix-increment, you have indeterminate sequencing of the two instances of x++ and thus unspecified behavior.

std::list<int> list{1,2,3,4,5,6,7}; auto x = begin(list); using T = decltype(x); void h(T, T); h(f(x++), g(x++)); struct X { X(T, T) {} } X(f(x++), g(x++));

And in the final case, you get full-blown undefined behavior as the two postfix-increments of x are unsequenced.

int x = 0; void h(int, int); h(f(x++), g(x++)); struct X { X(int, int) {} } X(f(x++), g(x++));