So I understand that re-usage of a variable that has been post incremented is undefined behavior in a function call. My understanding is this is not a problem in constructors. My question is about tie
which is oddly halfway between each.
Given: pair<int, int> func()
can I do:
tie(*it++, *it) = func();
Or is that undefined behavior?
Since C++17, this code has unspecified behavior. There are two possible outcomes:
the first argument is the result of dereferencing the original iterator, the second argument is the result of dereferencing the incremented iterator; or
the first argument and the second argument are both the result of dereferencing the original iterator.
Per [expr.call]/8:
[...] The initialization of a parameter, including every associated value computation and side effect, is indeterminately sequenced with respect to that of any other parameter. [...]
So the second argument to tie
may be either the result of dereferencing the incremented iterator or the original iterator.
Prior to C++17, the situation was a bit complicated:
if both ++
and *
invoke a function (e.g., when the type of it
is a sophisticated class), then the behavior was unspecified, similar to the case since C++17;
otherwise, the behavior was undefined.
Per N4140 (C++14 draft) [expr.call]/8:
[ 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 (see [intro.execution]). — end note ]
Thus, the code was undefined behavior because the evaluation of one argument was unsequenced with the other. The evaluation of the two arguments may overlap, resulting in a data race. Unless it is specified otherwise ...
Per N4140 [intro.execution]/15:
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 ] Every evaluation in the calling function (including other function calls) that is not otherwise specifically sequenced before or after the execution of the body of the called function is indeterminately sequenced with respect to the execution of the called function.9 Several contexts in C++ cause evaluation of a function call, even though no corresponding function call syntax appears in the translation unit. [ Example: Evaluation of a new expression invokes one or more allocation and constructor functions; see [expr.new]. For another example, invocation of a conversion function ([class.conv.fct]) can arise in contexts in which no function call syntax appears. — end example ] The sequencing constraints on the execution of the called function (as described above) are features of the function calls as evaluated, whatever the syntax of the expression that calls the function might be.
9) In other words, function executions do not interleave with each other.
Thus, if the operators are actually function calls, then the behavior is similarly unspecified.