We all know that the expression i++ + i++
is undefined behavior. But is it also UB, if the side effects happen on a reference in the function body? For example :
int f(int& i) { // pass by reference
return i++;
}
int main() {
int v=5;
auto r=f(v)+ 2*f(v);
cout << r <<endl;
}
When I read the standard, I understand that this is UB:
The standard explains that modifying a value when calling a function can be considered as side effect. So f()
would have a side effect on v
:
C++23/[intro.execution]/7: (...), modifying an object, calling a library I/O function, or calling a function that does any of those operations are all side effects, which are changes in the state of the execution environment.
The standard also explains that unsequenced side effects on the same memory location are UB. I therefore understand that the evaluation of the two operands of + are unsequenced:
C++23/[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 (6.9.2), the behavior is undefined.
Therefore, since both calls to f()
modify the same memory location v
, I understand it's UB. However some argue that the execution of the two called function bodies would be indeterminately sequenced, and not unsequenced, and hence the code would just deliver an unspecified result (17 if left call is executed first, and 16 if the contrary), without being UB. Can some language-lawyer enlighten me?
[intro.execution]/11 effectively states that every function invocation happens atomically with respect to sequencing, i.e. multiple function calls can't be interleaved.
Specifically here i++
for the left-hand invocation of f
is required to either be sequenced-before or sequenced-after, i.e. indeterminately-sequenced with, all evaluations in the right-hand side call to f
, because both happen on the same thread and the former evaluation does not occur within the latter function invocation.
As a result evaluations in different function calls (or different full-expressions in general) can't be unsequenced relative to one-another (if they happen on the same thread).
Therefore this is not undefined behavior. The two i++
evaluations are indeterminately sequenced, so you do not know which happens first, but they are not unsequenced.
There are however two valid observable behaviors, i.e. two different possible outputs, for either ordering of the function call evaluations.