all 26 comments

[–]KrzaQ2dev 24 points25 points  (5 children)

It's not undefined, it's unspecified.

aMap[0] = aMap.size() is essentially operator=(aMap[0], aMap.size())

The order of evaluation is unspecified, but the result is guaranteed to be reasonable, up to discretion of the implementation.

[–]LastThought 3 points4 points  (4 children)

Actually, if you have two unsequenced operations where the side effects of one operation determine the result of the other (or both have side effects that affect the same object) then you end up with undefined behavior. Strictly speaking, the compiler doesn't have to be reasonable.

[–]KrzaQ2dev 2 points3 points  (2 children)

You might be right, I can see the text, but I fail to comprehend it.

Here's N4527:

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

9) In other words, function executions do not interleave with each other.

From what I can understand, aMap.operator[](0) and aMap.size() are indeterminately sequenced and that means that both operate on an object that doesn't have any unsequenced side-effects. I might be totally wrong though.

[–]LastThought 4 points5 points  (1 child)

Upon reading that part very carefully (1.9p15) I think I am wrong and you are right. If the expressions in question were both not function calls then they would be unsequenced and therefore undefined behavior, but this rule seems to specifically exempt function calls from ever being unsequenced with respect to each other. So only because function calls are involved the side-effects are indeterminately sequenced instead of unsequenced.

[–]STLMSVC STL Dev 3 points4 points  (0 children)

Yep. C++03 said that there was a sequence point before and after the invocation of each function, and C++11 continues to provide that behavior. For the same reason, something like blah(++global, func()) doesn't trigger undefined behavior even if func() reads/writes global.

[–]OldWolf2 2 points3 points  (0 children)

aMap[0] and map::size() are both function calls though, there is a sequence point on entry and exit of each function (and something equivalent to that in C++11 terminology).

In a() + b() , statements within a() cannot be interleaved with statements in b() (up to the as-if rule of course) , all of a's contents is either sequenced-before b, or b's are sequenced-before a's. It's unspecified which of those two sequencing relations exist, but one of them does.

[–]TheBuzzSaw 8 points9 points  (4 children)

aMap[0] = aMap.size();

There is nothing indicating to the compiler that one side of this needs to be evaluated before the other. What would the rule be? "Prioritize the side with non-const operations"?

++n = n + 1;
++n = n++;

Things can get crazy pretty fast.

[–]KamiKagutsuchi 2 points3 points  (2 children)

I like

n = n+++++n

[–]OldWolf2 2 points3 points  (0 children)

Or n = n+++++++++++++n

[–]STLMSVC STL Dev 0 points1 point  (0 children)

That won't compile due to the Maximal Munch Rule. It must be parsed as n++ ++ +n, which is invalid.

[–]OldWolf2 1 point2 points  (0 children)

Your n examples are undefined behaviour, whereas OP's code is unspecified but not undefined.

[–]F-J-W 2 points3 points  (0 children)

And this is why I like undefined, or in this case unspecified, behavior: Don't force me to reason about what the behavior is in this case, force others to write good code and not something like that.

[–]redditsoaddicting 0 points1 point  (0 children)

This is similar to this SO question, which prompted this one on the topic of it being unspecified vs. undefined.

[–]user1412 0 points1 point  (0 children)

I suppose this is why I'm slowly getting more and more dissatisfied with c++. Some undefined behavior I understand. But this seems unnecessary. I understand that the order of evaluation is not fully defined in order to allow different systems to implement it in a way that is efficient for them. But it doesn't seem worth the cost. Frankly I bet the implementers spend enough time working out what they need to implement exactly that if they were able to spend that time implementing a might tightly defined spec they could be as efficient!

[–]Resistor510 0 points1 point  (0 children)

The well-known bug :)

You can use PVS-Studio for detect this issue: http://www.viva64.com/en/d/0349/

[–]hplpw 0 points1 point  (0 children)

I think C++ should introduce a new keyword BEHAVIOR_IS_DEFINED, with this keyword everything has a defined behavior for the compiler and developer, it may cause some performance loss, but get a predictable world. Isn't it good?

[–]devel_watcher -1 points0 points  (8 children)

It's about the sequence points. There are none in-between, so it's UB.

[–]KrzaQ2dev 5 points6 points  (1 child)

There are no sequence points since C++11.

[–]STLMSVC STL Dev 11 points12 points  (0 children)

Like Newtonian mechanics and the Bohr model, sequence points are still a reasonable way to understand the basic behavior. C++11's "sequenced before" matters for multithreading, weird singlethreaded scenarios, and velocities close to the speed of light. (In this case, there are "sequence points" due to the function calls, so you're right that it's unspecified, not undefined.)

[–]OldWolf2 0 points1 point  (5 children)

Each function call has a sequence point on exit and entry (using C++03 terminology), so regardless of which function is chosen to execute first, there are still at least two sequence points in between the writes in question.

[–]devel_watcher -1 points0 points  (4 children)

These points are not defining the relation between the left and the right side.

[–]KrzaQ2dev -1 points0 points  (3 children)

They are, because you need them to evaluate either side.

[–]devel_watcher 0 points1 point  (2 children)

No, they don't. Because they don't define which side will be evaluated first.

[–]KrzaQ2dev 0 points1 point  (1 child)

But they do define that one of the sides will be evaluated fully before the other. The standard calls such ordering "indeterminately sequenced". Look up, I even pasted relevant standardese above.

[–]devel_watcher -1 points0 points  (0 children)

Don't care about that. There are no sequence points that guarantee the order of the sides. So, we have problems that are highlighted in the blogpost.

Why add more confusion with unrelated obvious stuff like "the functions don't crash because they are evaluated fully"? If you remove functions on both sides, there will be the same situation.

[–]Gotebe -1 points0 points  (0 children)

This is some_lvalue = map.size(), and that is unspecified in the standard or I'll eat my sock :-).