Recently in an interview there was a following objective type question.
int a = 0;
cout << a++ << a;
Answers:
a. 10
b. 01
c. undefined behavior
I answered choice b, i.e. output would be "01".
But to my surprise later I was told by an interviewer that the correct answer is option c: undefined.
Now, I do know the concept of sequence points in C++. The behavior is undefined for the following statement:
int i = 0;
i += i++ + i++;
but as per my understanding for the statement cout << a++ << a
, the ostream.operator<<()
would be called twice, first with ostream.operator<<(a++)
and later ostream.operator<<(a)
.
I also checked the result on VS2010 compiler and its output is also '01'.
Sequence points only define a partial ordering. In your case, you have (once overload resolution is done):
There is a sequence point between the
a++
and the first call tostd::ostream::operator<<
, and there is a sequence point between the seconda
and the second call tostd::ostream::operator<<
, but there is no sequence point betweena++
anda
; the only ordering constraints are thata++
be fully evaluated (including side effects) before the first call tooperator<<
, and that the seconda
be fully evaluated before the second call tooperator<<
. (There are also causual ordering constraints: the second call tooperator<<
cannot preced the first, since it requires the results of the first as an argument.) §5/4 (C++03) states:One of the allowable orderings of your expression is
a++
,a
, first call tooperator<<
, second call tooperator<<
; this modifies the stored value ofa
(a++
), and accesses it other than to determine the new value (the seconda
), the behavior is undefined.Technically, overall this is Undefined Behavior.
But, there are two important aspects to the answer.
The code statement:
is evaluated as:
The standard does not define the order of evaluation of arguments to an function.
So Either:
std::operator<<(std::cout, a++)
is evaluated first ora
is evaluated first orThis order is Unspecified[Ref 1] as per the standard.
[Ref 1]C++03 5.2.2 Function call
Para 8
Further, there is no sequence point between evaluation of arguments to a function but a sequence point exists only after evaluation of all arguments[Ref 2].
[Ref 2]C++03 1.9 Program execution [intro.execution]:
Para 17:
Note that, here the value of
c
is being accessed more than once without an intervening sequence point, regarding this the standard says:[Ref 3]C++03 5 Expressions [expr]:
Para 4:
The code modifies
c
more than once without intervening sequence point and it is not being accessed to determine the value of the stored object. This is clear violation of the above clause and hence the result as mandated by the standard is Undefined Behavior[Ref 3].The correct answer is to question the question. The statement is unacceptable because a reader cannot see a clear answer. Another way to look at it is that we have introduced side-effects (c++) that make the statement much harder to interpret. Concise code is great, providing it's meaning is clear.
You can think of:
As:
C++ guarantees that all side effects of previous evaluations will have been performed at sequence points. There are no sequence points in between function arguments evaluation which means that argument
a
can be evaluated before argumentstd::operator<<(std::cout, a++)
or after. So the result of the above is undefined.C++17 update
In C++17 the rules have been updated. In particular:
Which means that it requires the code to produce result
b
, which outputs01
.See P0145R3 Refining Expression Evaluation Order for Idiomatic C++ for more details.