Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.
like image 205
einpoklum Avatar asked Aug 22 '17 10:08

einpoklum


3 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.

like image 180
StoryTeller - Unslander Monica Avatar answered Oct 05 '22 01:10

StoryTeller - Unslander Monica


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.

like image 34
Some programmer dude Avatar answered Oct 05 '22 03:10

Some programmer dude


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++));
like image 36
Deduplicator Avatar answered Oct 05 '22 01:10

Deduplicator