I think this part of the draft standard regarding order of evaluation is relevant:
1.9 Program Execution
...
- Except where noted, evaluations of operands of individual operators and of subexpressions of individual
expressions are unsequenced. The value computations of the operands of an
operator are sequenced before the value computation of the result of the operator. If a side effect on a scalar
object is unsequenced relative to either another side effect on the same scalar object or a value computation
using the value of the same scalar object, and they are not potentially concurrent, the behavior is
undefined
and also:
5.2.2 Function call
...
- [ Note: The evaluations of the postfix expression and of the arguments are all unsequenced relative to one
another. All side effects of argument evaluations are sequenced before the function is entered — end note ]
So for your line c.meth1(&nu).meth2(nu);
, consider what is happening in operator in terms of the function call operator for the final call to meth2
, so we clearly see the breakdown into the postfix expression and argument nu
:
operator()(c.meth1(&nu).meth2, nu);
The evaluations of the postfix expression and argument for the final function call (i.e. the postfix expression c.meth1(&nu).meth2
and nu
) are unsequenced relative to one another as per the function call rule above. Therefore, the side-effect of the computation of the postfix expression on the scalar object ar
is unsequenced relative to the argument evaluation of nu
prior to the meth2
function call. By the program execution rule above, this is undefined behaviour.
In other words, there is no requirement for the compiler to evaluate the nu
argument to the meth2
call after the meth1
call - it is free to assume no side-effects of meth1
affect the nu
evaluation.
The assembly code produced by the above contains the following sequence in the main
function:
- Variable
nu
is allocated on the stack and initialised with 0.
- A register (
ebx
in my case) receives a copy of the value of nu
- The addresses of
nu
and c
are loaded into parameter registers
meth1
is called
- The return value register and the previously cached value of
nu
in the ebx
register are loaded into parameter registers
meth2
is called
Critically, in step 5 above the compiler allows the cached value of nu
from step 2 to be re-used in the function call to meth2
. Here it disregards the possibility that nu
may have been changed by the call to meth1
- 'undefined behaviour' in action.
NOTE: This answer has changed in substance from its original form. My initial explanation in terms of side-effects of operand computation not being sequenced before the final function call were incorrect, because they are. The problem is the fact that computation of the operands themselves is indeterminately sequenced.
nu
,&nu
, andc
on to the stack in that order, then invokemeth1
, push the result on to the stack, then invokemeth2
, while a register-based calling convention would want to loadc
and&nu
into registers, invokemeth1
, loadnu
into a register, then invokemeth2
. – Lelia1
. Before C++17, the result can be either0
(like the OP's result) or1
. – Quoth