If !=
calls an overloaded comparison operator that takes the left argument (v
) by reference, the order of evaluation here does not really matter: binding a reference (directly) does not access the object denoted by the initializer. The actual comparison (in the body of the operator function) takes place after both operands are evaluated ([intro.execution]/11):
When calling a function [...], every value computation and side effect associated with any argument expression [...] is sequenced before execution of every expression or statement in the body of the called function.
In other words, the comparison is guaranteed behave as you expect here.
This does not hold if evaluating the left side involves reading the value of v
, as in the case of the built-in !=
operator, or an overload taking it by value (or requiring a conversion to bind a reference).
In that case, it becomes significant that the two operands are unsequenced ([intro.execution]/10):
Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced.
(The !=
operator does not have any special sequencing properties.)
There is potential for UB here:
If a side effect on a memory location is unsequenced relative to either another side effect on the same memory location or a value computation using the value of any object in the same memory location, and they are not potentially concurrent, the behavior is undefined.
However, [intro.execution]/11 applies:
For each function invocation or evaluation of an await-expression F, each evaluation that does not occur within F but is evaluated on the same thread and as part of the same signal handler (if any) is either sequenced before all evaluations that occur within F or sequenced after all evaluations that occur within F.
Summarized by a footnote:
In other words, function executions do not interleave with each other.
Which means the read of v
on the left and its modification on the right (which occurs within std::exchange
) can't conflict, and are effectively indeterminately sequenced: there's no UB, but no guarantee of consistent results either.
C++17 made some changes to the rules of evaluation order, but none are relevant here: this answer holds pre-C++17 as well.