This is actually a well-formed program that has two equally valid execution paths, so both compilers are right.
a[1] = a.size()
In this expression, the evaluation of the two operands of =
are unsequenced.
§1.9/15 [intro.execution] Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced.
However, function calls are not interleaved, so the calls to operator[]
and size
are actually indeterminately sequenced, rather than unsequenced.
§1.9/15 [intro.execution] 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.
This means that the function calls may happen in one of two orders:
operator[]
then size
size
then operator[]
If a key doesn't exist and you call operator[]
with that key, it will be added to the map, thereby changing the size of the map. So in the first case, the key will be added, the size will be retrieved (which is 1 now), and 1
will be assigned to that key. In the second case, the size will be retrieved (which is 0), the key will be added, and 0
will be assigned to that key.
Note, this is not a situation that brings about undefined behaviour. Undefined behaviour occurs when two modifications or a modification and a read of the same scalar object are unsequenced.
§1.9/15 [intro.execution] If a side effect on a scalar object is unsequenced relative to either another side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined.
In this situation, they are not unsequenced but indeterminately sequenced.
So what we do have is two equally valid orderings of the execution of the program. Either could happen and both give valid output. This is unspecified behaviour.
§1.3.25 [defns.unspecified]
unspecified behavior
behavior, for a well-formed program construct and correct data, that depends on the implementation
So to answer your questions:
Which compiler is right?
Both of them are.
Am I doing something wrong?
Probably. It's unlikely that you would want to write code that has two execution paths like this. Unspecified behaviour can be okay, unlike undefined behaviour, because it can be resolved to a single observable output, but it's not worth having in the first place if you can avoid it. Instead, don't write code that has this kind of ambiguity. Depending on what exactly you want correct path to be, you can do either of the following:
auto size = a.size();
a[1] = size; // value is 0
Or:
a[1];
a[1] = a.size(); // value is 1
If you want the result to be 1
and you know the key doesn't yet exist, you could of course do the first code but assign size + 1
.