Inspired by this blog
I already gave answer there but it struck me that it can be nice educational blog. Though main reason I write this is training English-writing.
I will refer to this document which is the closest I know to C++23 standard.
Disclaimer: Never mix several increments/decrements in single expression (even if each operation performed on different variables) — it is just looks ugly almost always and it is harder to read.
Why exactly expression ++x + ++x
is UB?
https://godbolt.org/z/qWbKfP9Gj. clang/gcc gives 13, msvc gives 14.
Let's explore how standard requires to calculate expressions. 6.9.1 is all about that. In particular, 6.9.5 introduces term full-expression with examples. For simplification let's say that every code between ;
(but not only they) is full-expression:
int f();
int main() {
std::cout << 2 + 2 << std::endl; // Whole line is full-expression
// 2 + 2 itself not full-expression
return f(); // Whole line is full-expression
}
6.9.8 introduces term sequenced before/after: A
is sequenced before B
meaning that every computation and side-effect needed for A
is executed strictly before every computation and side-effect of B
. And so defined sequenced after. And there introduced terms unsequenced and indeterminately sequenced: A
and B
unsequenced meaning that neither A
is sequenced before B
not B
is sequenced before A
. Therefore, evaluation of A
can overlaps with evaluation of B
. A
and B
indeterminately sequenced meaning that either A
is sequenced before B
or B
is sequenced before A
but there is no requirements about which is true.
And go to 6.9.10. There two things to be noted. First, any two expressions are unsequenced if standard doesn't says otherwise. So all cases of sequenced/indeterminately expressions must be explicitly defined in standard. Examples:
- 6.9.9 All computations and side-effects of full-expression are sequenced before any computation or side-effect of next full-expression. Meaning lines of code executed in order:)
- 7.6.1.6.1.
i++
. Obtaining value is sequenced before incrementing. - 7.6.14.
left && right
. Evaluation ofleft
is sequenced beforeright
. Moreover evaluation ofright
is taking place only ifleft
is true. - 7.6.19.1.
a = b
. Evaluation ifb
is sequenced beforea
. - 7.6.1.3.7. Function call
f(a, b)
.a
andb
are indeterminately sequenced. Fun fact: this appeared in C++17. So before C++17 argument evaluations were unsequenced.
Second thing to be noted from 6.9.10 is if side-effects of A
unsequenced with side-effect/computation of B
and same memory location is used then we got UB.
And this is exactly a case with ++x + ++x
: side-effect of both increments modifying same object x
, both increments evaluate this very object and standard says nothing about which of increments is sequenced first.
But there is operator precedence, isn't it?
Yep, but it is not about order of evaluation of operands. It is about evaluation of full-expression. operand evaluation still can be unsequenced.
int x = y++ + z++; // y++ is evaluated to y, z++ is evaluated to z.
// Both increments can be executed in any order (even simultaneously)
// But x is evaluated to y + z
std::cout << a() << b() << c(); // c() can be evaluated before a() or even at the same time, but result is printed strictly after both a() and b()
Bonus: a+++++b
what it is?
If you think that it is (a++)+(++b)
because this is only well-defined expression then you wrong:)
5.4.3 If we not talking about raw string literals or <::
then we always take longest possible token even if it produces ill-formed program. So it is parsed as tokens a ++ ++ + b
and it is compilation error.
If you have some good ideas about another C++ educational let me know.