Rectangle 27 1

gcc C++ Output evaluation order with embedded function calls?


// Option 1:  Both are members
cout.operator<<(f1 ()).operator<< (f2 ());

// Option 2: Both are non members
operator<< ( operator<<(cout, f1 ()), f2 () );

// Option 3: First is a member, second non-member
operator<< ( cout.operator<<(f1 ()), f2 () );

// Option 4: First is a non-member, second is a member
cout.operator<<(f1 ()).operator<< (f2 ());
cout << f1() << f2();
f1()
cout.operator<<(f1())
f2()
cout.operator<<(f1()).operator<<(f2());
f1()
f2()
cout.operator<<(f1())
cout.operator<<(f1()).operator<<(f2());
f2()
f1()
cout.operator<<(f1())
cout.operator<<(f1()).operator<<(f2());

At the lowest level these will generate almost identical code so I will refer only to the first option from now.

Nice :) I was about to write something similar, but it took me a while to check what exactly guarantees that the various operator<< calls are done in sequence. As you stated it makes sense to me, since then the implied object argument is just seen as a normal function argument which is evaluated before the function-entry sequence point. I thought that argument just existed for the purpose of overloading, but it seems that it has importance for this side-effect games too. +1 :)

The unspecified behaviour kicks in because although the calls to the operators must be ordered there is no such requirement on their arguments. Therefore, the resulting order can be one of:

There is a guarantee in the standard that the compiler must evaluate the arguments to each function call before the body of the function is entered. In this case, cout.operator<<(f1()) must be evaluated before operator<<(f2()) is, since the result of cout.operator<<(f1()) is required to call the other operator.

This is expanded to a sequence of function calls, where the kind of calls depend on the operators being members or non-members:

When I first looked at this example I felt that the behaviour was well defined because this expression is actually short hand for a set of function calls.

Note
Rectangle 27 1

gcc C++ Output evaluation order with embedded function calls?


std::cout << pow(x++,3) << endl << pow(x++,3) << endl << pow(x++,3) << endl;

+1. And in particular, note that the program doesn't exhibit undefined behavior. x is still only changed at most once between two sequence points, because function call executions cannot interleave each other. Its behavior is unspecified and after the cout expression statement, the value of x must be 5.

@onebyone: Very funny the "hate" part... :)

Edit to add: litb makes a point below about (un)defined behavior. The standard says that if you modify a variable multiple times in an expression, and if there exists a valid order of evaluation for that expression, such that the variable is modified multiple times without a sequence point in between, then the expression has undefined behavior. That doesn't apply here, because the variable is modified in the call to the function, and there's a sequence point at the start of any function call (even if the compiler inlines it). However, if you'd manually inlined the code:

GCC is not obliged to explain to you (or me) why it wants to order them as it does. It might be a performance optimisation, it might be because the compiler code came out a few lines shorter and simpler that way, it might be because one of the mingw coders personally hates you, and wants to ensure that if you make assumptions that aren't guaranteed by the standard, your code goes wrong. Welcome to the world of open standards :-)

Thanks for the explanation, I figured it was an instance of an undefined spec, but now I can be sure of it. And it can stand as an example to the students of why side effects and overly complicated nesting are bad :)

The C++ standard does not define what order the subexpressions of a full expression are evaluated, except for certain operators which introduce an order (the comma operator, ternary operator, short-circuiting logical operators), and the fact that the expressions which make up the arguments/operands of a function/operator are all evaluated before the function/operator itself.

Then that would be undefined behavior. In this code, it is valid for the compiler to evaluate all three "x++" subexpressions, then the three calls to pow, then start on the various calls to operator<<. Because this order is valid and has no sequence points separating the modification of x, the results are completely undefined. In your code snippet, only the order of execution is unspecified.

Note