operator precedence #2

Hi guys,

after a longer break, I continue with learning C++ and I arrived at the same problem I had in one of my last threads:
http://www.cplusplus.com/forum/beginner/225215/

A helpful quote from user @mbozzi:
"The only thing operator precedence tells you is which operands belong to which operators."

With this, I can understand the undefined behaviour of

1
2
int total {}; 
int sum = (total = 3) + total;


'=' is a 'weaker' operator than '+' so '=' will connect the lefthand side with the complete righthand side of the equation. So before sum gets a new value, the righthand side needs to be evaluated.
Again, '+' is stronger than '=', so the brackets are necessary if '+' is supposed to connect 'total' and the command 'total=3'.
But it is not clear what happens first: is 'total' set to 3 first and then added to 'total' (so that 'sum' would become 6) or is righthand 'total' evaluated first and added on the new 'total' that is 3 (so that sum would become 3).

But the following case still confuses me:
1
2
int i {2};
i = 3 * i++;

The book says there are two options:
1) first do 3*i, then give the result to i, then increment i (so i=7 in the end)
2) first do 3*i, then increment i, then give 3*i to i (so i=6 in the end)

From my point of view, 1) should not be possible. '=' has the weakest precedence, meaning it connects the operands 'i' and '3*i++'. So before doing both, the multiplication and the incrementation, the assignment can't happen.

Also what about the following case?
3) first increment i, then 3*i, then give result to i (so i=9)?

Regards
As an aside, speaking as an experienced C++ programmer, I don't care about operator precedence. I never learned it. I just write my code so it's clear what I intend to both reader and compiler.

Memorising the tiny details of operator precedence is fun if you like that kind of thing, but if your aim is to be a better C++ programmer, you're wasting time.
Last edited on
You shouldn't be using words like "first".
Consider the following:

1
2
3
4
5
6
7
8
9
10
#include <iostream>

int f() { std::cout << "f\n"; return 3; }
int g() { std::cout << "g\n"; return 4; }
int h() { std::cout << "h\n"; return 5; }

int main() {
    int a = f() + g() * h();
    std::cout << a << '\n';
}

If you think that g() * h() must happen "first", then you might think that the functions g and h must be called before f, but that's not true. They can be called in any order whatsoever.

Precedence is just about grouping, not about what is "first".

BTW, both of your examples are undefined behaviour.
From my point of view, 1) should not be possible. '=' has the weakest precedence, meaning it connects the operands 'i' and '3*i++'. So before doing both, the multiplication and the incrementation, the assignment can't happen.

We'll have to distinguish between the different kinds of evaluations.

When evaluating an expression:
- if the expression has a value, its value maybe needs to be determined in a process called value computation.
- if the expression has side-effects, those side effects maybe need to be started in a process called initiation of side-effects.

Both of these processes are independent. When an expression is evaluated, we look at all the required value computations and side-effects together, and order them according to a relation called sequenced-before. This relation tells us that for some pairs of evaluations, one occurs before the other.

For example, consider an ordering of a deck of cards by value, ignoring suit. We can't say that the six of clubs occurs before or after the six of hearts, but it certainly occurs before a seven of any suit. We'd say that such an relationship is a strict partial order.

Similarly, C++'s sequenced-before relation only guarantees that some evaluations occur before some others.

For example, in an expression containing i++, we've got a guarantee that the value of i++ is computed before the side-effect of i++ is initiated, which rules out case 3. But in the expression i = 3*i++, we don't have a guarantee that the side effect of the assignment happens before or after the side-effect of i++. It could happen either way.

cppreference wrote:
The side effect (modification of the left argument) of the built-in assignment operator and of all built-in compound assignment operators is sequenced after the value computation (but not the side effects) of both left and right arguments, and is sequenced before the value computation of the assignment expression (that is, before returning the reference to the modified object)

 — https://en.cppreference.com/w/cpp/language/eval_order


P.S.:
The rules have changed in C++17.
Last edited on
P.S.:
The rules have changed in C++17.

I'm just gonna ramble here, but: Honestly, the "changing undefined behavior to unspecified behavior" thing just makes it more confusing. It still doesn't make the code portable. Plus, no sane person should write code like that even if they know how the compiler will handle it. They should have just gone all the way (removing obvious undefined behavior), or not changed it.

@tpb
Nice example.
Just to solidify it to the OP, all of the following outputs (newlines removed for brevity) count as valid behavior in C++ (it's unspecified):

(EDIT: I think in C++17 the only possible outcome is f g h, however)

1
2
f g h
23

1
2
f h g
23

1
2
h f g
23

1
2
h g f
23

1
2
g h f
23

1
2
g f h
23


If a function has side-effects that the programmer needs to happen in a particular order, then the functions should be called on separate lines.
Last edited on
I'm just gonna ramble here, but: Honestly, the "changing undefined behavior to unspecified behavior" thing just makes it more confusing. It still doesn't make the code portable. Plus, no sane person should write code like that even if they know how the compiler will handle it. They should have just gone all the way (removing obvious undefined behavior), or not changed it.


In this particular case, C++17 aligns with OP's intuition - it fully specifies that expression.
Okay my mistake then. That makes me have questions (this C++17 stuff still confuses me) but maybe I'll ask them in another thread...
Thanks for the answers!


Repeater wrote:
As an aside, speaking as an experienced C++ programmer, I don't care about operator precedence. I never learned it. I just write my code so it's clear what I intend to both reader and compiler.

Memorising the tiny details of operator precedence is fun if you like that kind of thing, but if your aim is to be a better C++ programmer, you're wasting tim

I agree with that and I don't intend to memorize the rules. However, I should at least understand it, if I see it in a book, even if it is just for debugging purposes of code written by someone else.

@tpb
I understand your example and what you're saying, but not with respect to my points.

mbozzi wrote:


We'll have to distinguish between the different kinds of evaluations.

When evaluating an expression:
- if the expression has a value, its value maybe needs to be determined in a process called value computation.
- if the expression has side-effects, those side effects maybe need to be started in a process called initiation of side-effects.

Both of these processes are independent. When an expression is evaluated, we look at all the required value computations and side-effects together, and order them according to a relation called sequenced-before. This relation tells us that for some pairs of evaluations, one occurs before the other.

For example, consider an ordering of a deck of cards by value, ignoring suit. We can't say that the six of clubs occurs before or after the six of hearts, but it certainly occurs before a seven of any suit. We'd say that such an relationship is a strict partial order.

Similarly, C++'s sequenced-before relation only guarantees that some evaluations occur before some others.

For example, in an expression containing i++, we've got a guarantee that the value of i++ is computed before the side-effect of i++ is initiated, which rules out case 3.

Why does this rule out 3)? I calculate i++. Then I save this value into i (which is the side effect, right?).
Then I do my 3*i calculation...


But in the expression i = 3*i++, we don't have a guarantee that the side effect of the assignment happens before or after the side-effect of i++. It could happen either way.

Ok. It just feels strange to me that the '=' groups together the whole righthand side but the actual assignment can happen BEFORE everything on the righthand side is 'done'.
Last edited on
Why does this rule out 3)? I calculate i++. Then I save this value into i (which is the side effect, right?).
Then I do my 3*i calculation...

The computed value of an expression won't change retroactively. Imagine that the result is stored somewhere - so that when the value is required, it will be looked up instead of being value-computed again.

This multiplication is like E1 * E2, where E2 takes the value of i and later increments i, and E1 has the value 3.

That the value computation is sequenced before the side-effect is the difference between i++ and ++i.

Ok. It just feels strange to me that the '=' groups together the whole righthand side but the actual assignment can happen BEFORE everything on the righthand side is 'done'.

It is strange, enough so that C++17 made it so that every value computation and side effect on the right occurs before the same on the left. There are similar rules for many of the other binary operators.

So if you're using sufficiently up-to-date compiler, this discussion is moot.

The rationale is here:
https://wg21.link/p0145R3
PhysicsIsFun wrote:
The book says there are two options:

The book is wrong, there are either infinitely many options (before C++17, where the behavior of i = 3 * i++ was undefined: it had no meaning at all) or only one option (since C++17, where it is defined). What book was it?
Last edited on
So wait a second, "unspecified" is considered defined? Even C++17, online compiler, gives warning for i = 3 * i++; , while outputting 6.
Thanks, @Cubbi. I thought it was unspecified instead of undefined.

I'll trust you, since you wrote the reference page I'm getting my information from. ;)
@mbozzi that page has the very similar i = i++ + 2; // undefined behavior until C++17
mbozzi wrote:
The computed value of an expression won't change retroactively. Imagine that the result is stored somewhere - so that when the value is required, it will be looked up instead of being value-computed again.

I am not trying to change the value retroactively. The i in my calculation 3*i would be the 'new i', the one after I incremented it. So calculate i++. Then calculate 3*i (i being the incremented i), then save it to i.
Don't see why that wouldn't work, except if I take into account your mentioned rule from below, that calculation will happen before incrementation.

mbozzi wrote:

This multiplication is like E1 * E2, where E2 takes the value of i and later increments i, and E1 has the value 3.

That the value computation is sequenced before the side-effect is the difference between i++ and ++i.

Alright, so in any calculation dealing with i++, the value of i will be called for the calculation and only afterwards it gets incremented.

@Cubbi
It is "Der C++ Programmierer" (its German) from Ulrich Breyman. He does say that the behaviour is undefined, his reasoning being that there are two possibilities in what could happen there (first saving 3*i to i, then incrementing i, or calculating 3*i, incrementing i and then saving 3*i to i).
Last edited on
Note that there are two ++ operators.

1. The prefix ++ operator that gives you the value that the variable will have after it is incremented.
1
2
3
int a = 0;
int x = ++a;
std::cout << x; // prints 1 


2. The postfix ++ operator that gives you the value that the variable had before it was incremented.
1
2
3
int b = 0;
int y = b++;
std::cout << y; // prints 0 
PhysicsIsFun wrote:
He does say that the behaviour is undefined, his reasoning being that there are two possibilities in what could happen there

That's not "undefined", that's "unspecified". Undefined (which is the case here) means infinite possibilities in what could happen here. The C language specification and the C++ language specification until C++17 say this expression has no meaning.
Last edited on
Then the book is wrong, hopefully not at more important places =(

Thanks for the answers guys.
Topic archived. No new replies allowed.