c++language-lawyersequence-points

Order of evaluation in v != std::exchange(v, predecessor(v))


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


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:


Solution

  • 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:

    1. 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.