18
map<int, int> mp;
printf("%d ", mp.size());
mp[10]=mp.size();
printf("%d\n", mp[10]);

This code yields an answer that is not very intuitive:

0 1

I understand why it happens - the left side of the assignment returns reference to mp[10]'s underlying value and at the same time creates aforementioned value, and only then is the right side evaluated, using the newly computed size() of the map.

Is this behaviour stated anywhere in C++ standard? Or is the order of evaluation undefined?

Result was obtained using g++ 5.2.1.

Shafik Yaghmour
  • 143,425
  • 33
  • 399
  • 682
akrasuski1
  • 752
  • 7
  • 24
  • That is really curious. gcc 4.8.4 returns 0 1 as well, but clang 3.4 returns 0 0. – Harald Nov 08 '15 at 21:05
  • 3
    You should be careful with the word "undefined". It is not synonymous with "unspecified". – molbdnilo Nov 08 '15 at 21:07
  • And to add to redcrash's statement, gcc 4.9.2 and clang 3.8 as of Friday evening are both following the 4.8.4 and 3.4 results. Further reinforcing that there is no requirement somewhere to have a specific order for this. – Mats Petersson Nov 08 '15 at 21:11
  • 1
    Interesting question. Take a look [here](https://ideone.com/serR0z). – erip Nov 08 '15 at 21:13
  • @erip: Yes, not surprising, I'd say, since the constructor of `std::pair` must be completed before the call to `map::insert`. – Mats Petersson Nov 08 '15 at 21:40

4 Answers4

19

Yes, this is covered by the standard and it is unspecified behavior. This particular case is covered in a recent C++ standards proposal: N4228: Refining Expression Evaluation Order for Idiomatic C++ which seeks to refine the order of evaluation rules to make it well specified for certain cases.

It describes this problem as follows:

Expression evaluation order is a recurring discussion topic in the C++ community. In a nutshell, given an expression such as f(a, b, c), the order in which the sub-expressions f, a, b, c are evaluated is left unspecified by the standard. If any two of these sub-expressions happen to modify the same object without intervening sequence points, the behavior of the program is undefined. For instance, the expression f(i++, i) where i is an integer variable leads to undefined behavior , as does v[i] = i++. Even when the behavior is not undefined, the result of evaluating an expression can still be anybody’s guess. Consider the following program fragment:

#include <map>

int main() {
  std::map<int, int>  m;
  m[0] = m.size(); // #1
}

What should the map object m look like after evaluation of the statement marked #1? { {0, 0 } } or {{0, 1 } } ?

We know that unless specified the evaluations of sub-expressions are unsequenced, this is from the draft C++11 standard section 1.9 Program execution which says:

Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced.[...]

and all the section 5.17 Assignment and compound assignment operators [expr.ass] says is:

[...]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.[...]

So this section does not nail down the order of evaluation but we know this is not undefined behavior since both operator [] and size() are function calls and section 1.9 tells us(emphasis mine):

[...]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[...]

Note, I cover the second interesting example from the N4228 proposal in the question Does this code from “The C++ Programming Language” 4th edition section 36.3.6 have well-defined behavior?.

Update

It seems like a revised version of N4228 was accepted by the Evolution Working Group at the last WG21 meeting but the paper(P0145R0) is not yet available. So this could possibly no longer be unspecified in C++17.

Update 2

Revision 3 of p0145 made this specified and update [expr.ass]p1:

The assignment operator (=) and the compound assignment operators all group right-to-left. All require a modifiable lvalue as their left operand; their result is an lvalue referring to the left operand. The result in all cases is a bit-field if the left operand is a bit-field. 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. ...

Shafik Yaghmour
  • 143,425
  • 33
  • 399
  • 682
  • 1
    Note, in order to show that this is unspecified versus undefined behavior we need to quote section `1.9`, quoting section `5.17` is not sufficient. – Shafik Yaghmour Nov 09 '15 at 15:29
  • 1
    Note, as of [proposal p0145r3](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0145r3.pdf) this is no longer unspecified behavior. §[expr.ass] now says `The right operand is sequenced before the left operand` – Shafik Yaghmour Oct 27 '17 at 17:29
  • Given an expression like `*f(foo()) = x;` or `*(foo()) += x;`, where `x` might be accessible to function `foo`, would a compiler be required to read x, save it somewhere, and then call `foo`, retrieve the saved value, and then store or add it to `foo`? That would seem like it would add expense on some platforms, and of the compilers I've tested, only clang seems to actually work that way in C++17 mode. – supercat Aug 13 '18 at 20:19
8

From the C++11 standard (emphasis mine):

5.17 Assignment and compound assignment operators

1 The assignment operator (=) and the compound assignment operators all group right-to-left. All require a modifiable lvalue as their left operand and return an lvalue referring to the left operand. The result in all cases is a bit-field if the left operand is a bit-field. 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.

Whether the left operand is evaluated first or the right operand is evaluated first is not specified by the language. A compiler is free to choose to evaluate either operand first. Since the final result of your code depends on the order of evaluation of the operands, I would say it is unspecified behavior rather than undefined behavior.

1.3.25 unspecified behavior

behavior, for a well-formed program construct and correct data, that depends on the implementation

Community
  • 1
  • 1
R Sahu
  • 196,807
  • 13
  • 136
  • 247
3

I'm sure that the standard does not specify for an expression x = y; which order x or y is evaluated in the C++ standard (this is the reason why you can't do *p++ = *p++ for example, because p++ is not done in a defined order).

In other words, to guarantee order x = y; in a defined order, you need to do break it up into two sequence points.

 T tmp = y;
 x = tmp;

(Of course, in this particular case, one might presume the compiler prefers to do operator[] before size() because it can then store the value directly into the result of operator[] instead of keeping it in a temporary place, to store it later after operator[] has been evaluated - but I'm pretty sure the compiler doesn't NEED to do it in that order)

Mats Petersson
  • 119,687
  • 13
  • 121
  • 204
1

Let's take a look at what your code breaks down to:

mp.operator[](10).operator=(mp.size());

which pretty much tells the story that in the first part an entry to 10 is created and in the second part the size of the container is assigned to the integer reference in position of 10.

But now you get into the order of evaluation problem which is unspecified. Here is a much simpler example .

When should map::size() get called, before or after map::operator(int const &); ?

Nobody really knows.

Community
  • 1
  • 1
g24l
  • 2,819
  • 11
  • 27