Question:
There are several points in the description of the assignment operator (assignment operator) in the language standard that are not clear to me, and I would like to clarify them.
n4659, 8.18/1:
[…] In all cases, the assignment is sequenced after the value computation of the right and left operands , and before the value computation of the assignment expression. The right operand is sequenced before the left operand. With respect to an indeterminately-sequenced function call, the operation of a compound assignment is a single evaluation. [ Note : Therefore, a function call shall not intervene between the lvalue-to-rvalue conversion and the side effect associated with any single compound assignment operator. —end note ]
Question 1. What is value computation and is it performed in terms of the standard for left and right operands in the following code:
a = 1;
Question 2. What is meant by the indefinite ordering of a function call? I would like to see an example involving an assignment and a function call in the same expression, where the function call is indefinitely ordered relative to something.
n4659, 8.18/8:
If the value being stored in an object is read via another object that overlaps in any way the storage of the first object, then the overlap shall be exact and the two objects shall have the same type, otherwise the behavior is undefined. [ Note : This restriction applies to the relationship between the left and right sides of the assignment operation; it is not a statement about how the target of the assignment may be aliased in general. see 6.10. —end note ]
Question 3. I would like to see an example that fits the description of the above quote.
Addendum to question 1.
Perhaps it makes more sense to put the question as follows: is value computation always performed on the operands of an assignment operator, from the point of view of the language standard? And if not, when is it done and when is it not?
The point is this. The first quote in this question says that the assignment occurs after the value computation of both operands has occurred. It also says that the right operand is ordered before the left operand (i.e. the value computations + side effects associated with the right operand will occur before the value computations + side effects associated with the left operand).
Thus, if in an assignment operator the value computation of the left operand always occurs before the actual assignment, then the right operand is always ordered before the assignment.
Answer:
"value computation" is the process of calculating the value of an expression, i.e. getting its result. For example, let's say we have int i = 0
, then the evaluated expression i++
will return 0
. The important thing here is that i++
contains side effects. So, having calculated the above expression, we do not guarantee that the required changes (increment) were applied to it at the same time. Why might this be important? Let's take for this standard up to 17 years. In it, the expression: i = i++
has undefined behavior, because the increment for i
is not ordered in any way with respect to the assignment: it can occur before or after.
On the other hand, if we take the expression ++i
, then in it the side effects are applied before the expression is evaluated, which makes such an expression i = ++i
work and not contain undefined behavior.
Now to the second question. Let's assume we have a global variable: long long global = 0;
and the function auto foo() {return global += 1;}
. For example, we also need such a function void ignore(long long, long long)
and C++17 (solely for the sake of guaranteeing indeterminately sequenced evaluation of function arguments).
Finally, consider the following code: ignore(global += BIG_NUMBER, foo())
, so, in theory, if you remove this rule, you could get the following situation (on some platform, where the long long
entry will be in several instructions ): we start writing to our global
from the outside, we write a part, but then there is a switch to the foo
code (as the compiler decided) and rewrites what we wrote. After that, we continue recording and our global
variable contains garbage. Those. this rule forbids interleaving (interleaving, overlapping) the evaluation of expressions, and the compiler has no right to do what I described in the previous sentence.
But this is all theory, because the standard explicitly forbids (C++14[intro.object]p13) interleaving of all expressions, except for unsequenced ones, so such clarifications seem superfluous to me.
The third quote forbids this:
int i = 0;
char* ptr = reinterpret_cast<char*>(&i);
// Вот тут проблема
*ptr = static_cast<char>(i);
Those. there should not be two objects on the left and on the right that point to the same memory area, but have different types.
Regarding the addition: it's just worth quoting from C++17[intro.object]p15 here:
<…> An expression X is said to be sequenced before an expression Y if every value computation and every side effect associated with the expression X is sequenced before every value computation and every side effect associated with the expression Y <…>
And that will be enough. According to the rules introduced in C++17, the right side of an assignment expression is ordered before (sequenced before):
The right operand is sequenced before the left operand
This is the key to understanding: before we assign a new value, or even begin to calculate what we assign there, everything on the right must work out. This rule makes code i = i++
correct, although this was undefined behavior prior to C++17.