I keep finding more idioms that lend themselves to std::exchange.
Today I found myself writing this in an answer:
do {
path.push_front(v);
} while (v != std::exchange(v, pmap[v]));
I like it a lot more than, say
do {
path.push_front(v);
if (v == pmap[v])
break;
v= pmap[v];
} while (true);
Hopefully for obvious reasons.
However, I'm not big on standardese and I can't help but worry that lhs != rhs
doesn't guarantee that the right-hand side expression isn't fully evaluated before the left-hand-side. That would make it a tautologous comparison - which would by definition return true.
The code, however, does run correctly, apparently evaluating lhs first.
Does anyone know
- whether the standard guarantees this evaluation order
- if it has changed in recent standards, which standard version first specified it?
PS. I realize that this is a special case of f(a,b)
where f
is operator!=
. I've tried to answer my own query using the information found here but have failed to reach a conclusion to date:
- https://en.cppreference.com/w/cpp/language/eval_order
- https://en.wikipedia.org/wiki/Sequence_point
- Order of evaluation in C function parameters
- What are the evaluation order guarantees introduced by C 17?
CodePudding user response:
C 17 introduced rules on sequences. What was UB before is now well defined. This applies to arguments to function calls as well as a select assortment of operators:
sequenced before is an asymmetric, transitive, pair-wise relationship between evaluations within the same thread.
- If A is sequenced before B (or, equivalently, B is sequenced after A), then evaluation of A will be complete before evaluation of B begins.
The built-in !=
however is not sequenced (see link above). A function call would be sequenced but the order of evaluation is not guaranteed:
- In a function call, value computations and side effects of the initialization of every parameter are indeterminately sequenced with respect to value computations and side effects of any other parameter.
(emphasis added)
To my reading, even if you wrote a wrapper function, your compiler would not be required to evaluate v
first, then std::exchange(v, pmap[v])
and finally equal(..)
. And reversing the evaluation order, I believe, would change semantics in your example.
So sadly, as nice as std::exchange
is, in this case, it is not guaranteed to do what you need it to.
CodePudding user response:
This is UB per [intro.execution]/10:
Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced.
[...] 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.
(The !=
operator does not have any special sequencing properties.)
Whether !=
is overloaded for v
's type does not affect sequencing rules (since you're not calling it using function call notation) ([over.match.oper]/2):
[...] the operator notation is first transformed to the equivalent function-call notation [...] However, the operands are sequenced in the order prescribed for the built-in operator.
(And even if you did use the function call notation, the operands would still be indeterminately sequenced.)
However, if the operator is overloaded and takes the values by reference, the behavior becomes well-defined: the execution of the operator will happen after both arguments are evaluated, which means it'll read the post-exchange value of v
in any case and compare it to the temporary materialized from exchange
's return value (UB is avoided because binding a reference does not access the object).
Also (assuming built-in or by-value overloaded !=
), even if the left-hand side of !=
were sequenced before the right-hand side, the condition in your first snippet would always be false
- you have the order of operands mixed up. Which I think speaks volumes about which way of writing this code is clearer.