Order of Execution in Operator <<

Order of execution in operator

The compiler can evaluate the function printcbase() as this:

void printcbase(cbase* b ){
int a = b->FooA(); // line 1
int b = b->FooB(); // line 2
std::cout << a; // line 3
std::cout << b; // line 4
stc::cout << std::endl;
}

or some of many permutatins of lines marked as 1 - 4. You are only guaranteed that that the line 1 is done before the line 3, and line 2 before the line 4 (and of course line 3 before line 4). Standard does not say more and indeed you can expect different results with different C++ compilers.

order of function call inside cout

As stated here: https://en.cppreference.com/w/cpp/language/eval_order

Order of evaluation of any part of any expression, including order of
evaluation of function arguments is unspecified (with some exceptions
listed below). The compiler can evaluate operands and other
subexpressions in any order, and may choose another order when the
same expression is evaluated again.

There is no concept of left-to-right or right-to-left evaluation in
C++. This is not to be confused with left-to-right and right-to-left
associativity of operators: the expression a() + b() + c() is parsed
as (a() + b()) + c() due to left-to-right associativity of operator+,
but the function call to c may be evaluated first, last, or between
a() or b() at run time

In your line

cout << sum(3) << sum(2)

the order of the two operator<< calls depends on the operator you use (here << so left-to-right), but the evaluation of each subexpression, namely sum(3) and sum(2) has no defined order and depends on the mood (most optimized compile approach usually) of your compiler.

For info here is a list of operators associativity: https://en.cppreference.com/w/cpp/language/operator_precedence

What are the evaluation order guarantees introduced by C++17?

Some common cases where the evaluation order has so far been unspecified, are specified and valid with C++17. Some undefined behaviour is now instead unspecified.

i = 1;
f(i++, i)

was undefined, but it is now unspecified. Specifically, what is not specified is the order in which each argument to f is evaluated relative to the others. i++ might be evaluated before i, or vice-versa. Indeed, it might evaluate a second call in a different order, despite being under the same compiler.

However, the evaluation of each argument is required to execute completely, with all side-effects, before the execution of any other argument. So you might get f(1, 1) (second argument evaluated first) or f(1, 2) (first argument evaluated first). But you will never get f(2, 2) or anything else of that nature.

std::cout << f() << f() << f();

was unspecified, but it will become compatible with operator precedence so that the first evaluation of f will come first in the stream (examples below).

f(g(), h(), j());

still has unspecified evaluation order of g, h, and j. Note that for getf()(g(),h(),j()), the rules state that getf() will be evaluated before g, h, j.

Also note the following example from the proposal text:

 std::string s = "but I have heard it works even if you don't believe in it"
s.replace(0, 4, "").replace(s.find("even"), 4, "only")
.replace(s.find(" don't"), 6, "");

The example comes from The C++ Programming Language, 4th edition, Stroustrup, and used to be unspecified behaviour, but with C++17 it will work as expected. There were similar issues with resumable functions (.then( . . . )).

As another example, consider the following:

#include <iostream>
#include <string>
#include <vector>
#include <cassert>

struct Speaker{
int i =0;
Speaker(std::vector<std::string> words) :words(words) {}
std::vector<std::string> words;
std::string operator()(){
assert(words.size()>0);
if(i==words.size()) i=0;
// Pre-C++17 version:
auto word = words[i] + (i+1==words.size()?"\n":",");
++i;
return word;
// Still not possible with C++17:
// return words[i++] + (i==words.size()?"\n":",");

}
};

int main() {
auto spk = Speaker{{"All", "Work", "and", "no", "play"}};
std::cout << spk() << spk() << spk() << spk() << spk() ;
}

With C++14 and before we may (and will) get results such as

play
no,and,Work,All,

instead of

All,work,and,no,play

Note that the above is in effect the same as

(((((std::cout << spk()) << spk()) << spk()) << spk()) << spk()) ;

But still, before C++17 there was no guarantee that the first calls would come first into the stream.

References: From the accepted proposal:

Postfix expressions are evaluated from left to right. This includes
functions calls and member selection expressions.

Assignment expressions are evaluated from right to left. This
includes compound assignments.

Operands to shift operators are evaluated from left to right. In
summary, the following expressions are evaluated in the order a, then
b, then c, then d:

  1. a.b
  2. a->b
  3. a->*b
  4. a(b1, b2, b3)
  5. b @= a
  6. a[b]
  7. a << b
  8. a >> b

Furthermore, we suggest the following additional rule: the order of
evaluation of an expression involving an overloaded operator is
determined by the order associated with the corresponding built-in
operator, not the rules for function calls.

Edit note: My original answer misinterpreted a(b1, b2, b3). The order of b1, b2, b3 is still unspecified. (thank you @KABoissonneault, all commenters.)

However, (as @Yakk points out) and this is important: Even when b1, b2, b3 are non-trivial expressions, each of them are completely evaluated and tied to the respective function parameter before the other ones are started to be evaluated. The standard states this like this:

§5.2.2 - Function call 5.2.2.4:

. . .
The postfix-expression is sequenced before each expression in the
expression-list and any default argument. Every value computation and
side effect associated with the initialization of a parameter, and the
initialization itself, is sequenced before every value computation and
side effect associated with the initialization of any subsequent
parameter.

However, one of these new sentences are missing from the GitHub draft:

Every value computation and side effect associated with the
initialization of a parameter, and the initialization itself, is
sequenced before every value computation and side effect associated
with the initialization of any subsequent parameter.

The example is there. It solves a decades-old problems (as explained by Herb Sutter) with exception safety where things like

f(std::unique_ptr<A> a, std::unique_ptr<B> b);

f(get_raw_a(), get_raw_a());

would leak if one of the calls get_raw_a() would throw before the other
raw pointer was tied to its smart pointer parameter.

As pointed out by T.C., the example is flawed since unique_ptr construction from raw pointer is explicit, preventing this from compiling.*

Also note this classical question (tagged C, not C++):

int x=0;
x++ + ++x;

is still undefined.

Order of evaluation of arguments using std::cout

The evaluation order of elements in an expression is unspecified (except some very particular cases, such as the && and || operators and the ternary operator, which introduce sequence points); so, it's not guaranteed that test will be evaluated before or after foo(test) (which modifies it).

If your code relies on a particular order of evaluation the simplest method to obtain it is to split your expression in several separated statements.

cout order of call to functions it prints?

There's no sequence point with the << operator so the compiler is free to evaluate either dequeue function first. What is guaranteed is that the result of the second dequeue call (in the order in which it appears in the expression and not necessarily the order in which it is evaluated) is <<'ed to the result of <<'ing the first (if you get what I'm saying).

So the compiler is free to translate your code into some thing like any of these (pseudo intermediate c++). This isn't intended to be an exhaustive list.

auto tmp2 = myQueue.dequeue();
auto tmp1 = myQueue.dequeue();
std::ostream& tmp3 = cout << tmp1;
tmp3 << tmp2;

or

auto tmp1 = myQueue.dequeue();
auto tmp2 = myQueue.dequeue();
std::ostream& tmp3 = cout << tmp1;
tmp3 << tmp2;

or

auto tmp1 = myQueue.dequeue();
std::ostream& tmp3 = cout << tmp1;
auto tmp2 = myQueue.dequeue();
tmp3 << tmp2;

Here's what the temporaries correspond to in the original expression.

cout << myQueue.dequeue() << myQueue.dequeue();
| | | | |
| |____ tmp1 _____| |_____ tmp2 ____|
| |
|________ tmp3 _________|

Is there a sequence point between a function call returning an object and a method call on that object?

The answer depends on which version of the C++ standard you are using (or your compiler is using).

C++ 2003 5.2.2 p8 said:

The order of evaluation of arguments is unspecified. All side effects of argument expression evaluations take effect before the function is entered. The order of evaluation of the postfix expression and the argument expression list is unspecified.

This means there is not a sequence point between evaluating f(x) and args.

In C++ 2011 the whole concept of sequence points has been replaced (see N1944), and that wording is now just a note:

[ Note: The evaluations of the postfix expression and of the argument expressions are all unsequenced relative to one another. All side effects of argument expression evaluations are sequenced before the function is entered (see 1.9). — end note ]

and 1.9 p15 says

When calling a function (whether or not the function is inline), every value computation and side effect associated with any argument expression, or with the postfix expression designating the called function, is sequenced before execution of every expression or statement in the body of the called function. [ Note: Value computations and side effects associated with different argument expressions are unsequenced. — end note ]

This says the expression f(x) and the expression args are sequenced before everything in the body of g, but that they are unsequenced relative to each other, which is the same as the C++03 rules but worded differently.

C++14 has the same rules as C++11, but as noted in the comment below, the rules changed in C++17.

C++ 2017 8.2.2 [expr.call] p5 says:

The postfix-expression is sequenced before each expression in the expression-list and any default argument. The initialization of a parameter, including every associated value computation and side effect, is indeterminately sequenced with respect to that of any other parameter.

This means for your example the following steps happen in order:

  • f is evaluated.
  • x is evaluated and the parameters of f are initialized.
  • The function call f(x) is evaluated.
  • f(x)->g is evaluated.
  • args and the other arguments to g are evaluated and the parameters of g are initialized (in an unspecified order).
  • Finally, the function call f(x)->g(args, ...) is evaluated.

Confusing example about '&&' and '||' precedence

The expression is short-circuiting. From the link:

when the first argument of the AND function evaluates to false, the overall value must be false; and when the first argument of the OR function evaluates to true, the overall value must be true.

It sees that the rest of the condition doesn't matter because one of the operands of || is already true (10 < 20). If one of the operands is true, then no matter what the rest of the condition is, it's true.

You may use bitwise & and | to prevent this.



But, the ( expr2 && expr3 ) should be evaluated before expr1, no ?

No. You have to separate concepts of precedence and evaluation order.

  • Precedence: Dictates the parenthesization of an expression, not the order in which an expression is evaluated. For an example:

      true || false && false

    Is parenthesized to this because && has higher precedence than ||:

      true || (false && false)

    This does not mean that things in parentheses is evaluated first in Java's case. Precedence just clarifies what the operands of an operator are, in this case false and false, where as in this case:

      (true || false) && (false || false)

    The operands for && are true and false, not false and false.

  • Evaluation Order: Describes in what order each operand is evaluated and operator is applied and is sometimes language specific. This dictates how an expression is evaluated, unlike precedence.

In this case, your example:

true || false && false

As established earlier, becomes this due to precedence:

true || (false && false)

But Java, unlike C++, JavaScript, or a number of other languages has a strictly left to right evaluation. Per the Java Language Specification:

15.7. Evaluation Order

The Java programming language guarantees that the operands of operators appear to be evaluated in a specific evaluation order, namely, from left to right.

15.7.1. Evaluate Left-Hand Operand First

The left-hand operand of a binary operator appears to be fully evaluated before any part of the right-hand operand is evaluated.

So, when you have:

true || (false && false)

Java first evaluates the left operand which turns out to be true. Then the whole condition short circuits. The right operand of || in the parentheses is never evaluated at all. The same goes for your other example:

a1 < a2 || (++a1 > a2 && ++a2 < a1)
^^^^^^^^^^^^^^^^^^^^^^^^
Step 0, precedence and parenthesization


a1 < a2 || (++a1 > a2 && ++a2 < a1)
^^^^^^^
Step 1, left operand evaluated, variables resolved to values 10 and 20, condition is true


true || (++a1 > a2 && ++a2 < a1)
^^^^
Step 2, short circuits, left operand is not evaluated

Take another more complex example:

false || false || true && (false || true) || false

Due to precedence, it becomes:

false || false || (true && (false || true)) || false

Then, evaluation begins, left to right:

false || false || (true && (false || true)) || false
^^^^^^^^^^^^^^
Step 1, false || false, does not short circuit, right operand is evaluated, is false


false || (true && (false || true)) || false
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Step 2, false || (true && (false || true)), does not short circuit, right operand is evaluated
Step 2A, (true && (false || true)), does not short circuit, right operand is evaluated
Step 2B, (false || true), does not short circuit, right operand is evaluated, is true
Step 2C, (true && true), does not short circuit, right operand is evaluated, is true
Step 2D, false || true, does not short circuit, right operand is evaluated, is true


true || false
^^^^
Step 3, true || false short circuits, right operand is not evaluated, is true

Thus the whole expression evaluates to true. The whole expression was evaluated left to right the whole way through. Precedence only dictated the operands of an operator via parenthesization, not the evaluation order.

Further reading at Eric Lippert's explanatory article on precedence vs associativity vs evaluation order as mentioned by Daniel Pryden, it clears up a lot of the confusion.

The main takeaway is that precedence does not dictate in what an expression is evaluated. It only dictates how an expression should be parenthesized. Evaluation order, on the other hand, tells us exactly how an expression is evaluated, and in Java's case is always left to right.



Related Topics



Leave a reply



Submit