What is the correct answer for cout << a++ << a;?
Asked Answered
E

4

100

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'.

Eczema answered 28/5, 2012 at 10:9 Comment(13)
Did you ask for an explanation? I often interview potential candidates and am quite interested in receiving questions, it shows interest.Allium
Exactly, did you get an answer out from the interviewer? He is absolutely wrong. The output is always 01.Pluri
No I didn't ask, rather I missed to ask. as initially there was only one person on panel and while we were discussing this the other arrived and somehow we moved to some other topic on discussion and I missed this. Later I was fussing-off.Eczema
@Ashwinkumar It could be either 01 or 10.Hypophysis
@Hypophysis Agreed.. It could be 01 or 10 or undefined.. Understanding how..Pluri
@Hypophysis It's undefined behavior. Anything the implementation does (including sending an insulting email in your name to your boss) is conformant.Kratzer
This question is crying out for a C++11 (the current version of C++) answer that doesn't mention sequence points. Unfortunately I'm not knowledgeable enough about the replacement for sequence points in C++11.Marje
If it wasn't undefined it definitely couldn't be 10, it would be either 01 or 00. (c++ will always evaluate to the value c had before being incremented). And even if it wasn't undefined it would still be horribly confusing.Neocene
@Charles: James Kanze's answer develops the ordered-before relationship based on sequence points... the remainder of his answer, which depends only on the partial order, is completely applicable under the C++11 rules.Mcgraw
@BenVoigt: It may reach the correct conclusion for C++11 but the answer is built on the concept of sequence point. What I meant was that I'd like to see an answer constructed from the concepts and rules of C++11 which demonstrates that it is correct, not one that reaches a conclusion that happens to be correct for C++11 also.Marje
Ya know, when I read the title “cout << c++ << c”, I momentarily thought of it as a statement about the relationship between the C and C++ languages, and some other one named “cout”. You know, like someone was saying how they thought that “cout” was much inferior to C++, and that C++ was much inferior to C — and probably by transitivity that “cout” was very, very much inferior to C. :)Loiret
Out of curiosity, why is alternative a 10? Shouldn't it rather be 00?Sabah
Please note that the fact that a compiled program outputs 01 does not in any way mean that the behavior of the code is defined.Sabah
V
148

You can think of:

cout << a++ << a;

As:

std::operator<<(std::operator<<(std::cout, a++), a);

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 argument std::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:

In a shift operator expression E1<<E2 and E1>>E2, every value computation and side-effect of E1 is sequenced before every value computation and side effect of E2.

Which means that it requires the code to produce result b, which outputs 01.

See P0145R3 Refining Expression Evaluation Order for Idiomatic C++ for more details.

Vincentia answered 28/5, 2012 at 10:17 Comment(6)
@Maxim: Thanks for the expalanation. With the calls you expained it would be undefined behaviour. But now, I have one more question (may be siller one, and I missing something basic and thinking loud) How did you deduce that the global version of std::operator<<() would be called instead of ostream::operator<<() member version. On debugging I am landing in a member version of ostream::operator<<() call rather than global version and that's the reason that initially I thought that answer would be 01Eczema
@Maxim Not that it makes a different, but since c has type int, the operator<< here are member functions.Kratzer
@pravs: whether operator<< is a member function or a free-standing function doesn't affect sequence points.Vincentia
Right, thanks James/Maxim I told you I am missing something very basic. I got confused that with the member function declaration of: _Myt& __CLR_OR_THIS_CALL operator<<(int _Val) And what I was missing in my thinking that the first argument is "this" for member function, so got bit confused with the function calls. :) cheers!Eczema
The 'sequence point' is no longer used in the C++ standard. It was imprecise and has been replaced with the 'sequenced before/sequenced after' relation.Conductivity
So the result of the above is undefined. Your explanation is only good for unspecified, not for undefined. JamesKanze explained how it the more damning undefined in his answer though.Obeng
T
68

Technically, overall this is Undefined Behavior.

But, there are two important aspects to the answer.

The code statement:

std::cout << a++ << a;

is evaluated as:

std::operator<<(std::operator<<(std::cout, a++), a);

The standard does not define the order of evaluation of arguments to an function.
So Either:

  • std::operator<<(std::cout, a++) is evaluated first or
  • ais evaluated first or
  • it might be any implementation defined order.

This order is Unspecified[Ref 1] as per the standard.

[Ref 1]C++03 5.2.2 Function call
Para 8

The order of evaluation of arguments is unspecified. All side effects of argument expression evaluations take effect before the function is entered. The order of evaluation of the postfix expression and the argument expression list is unspecified.

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:

When calling a function (whether or not the function is inline), there is a sequence point after the evaluation of all function arguments (if any) which takes place before execution of any expressions or statements in the function body.

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:

....
Between the previous and next sequence point a scalar object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be accessed only to determine the value to be stored. The requirements of this paragraph shall be met for each allowable ordering of the subexpressions of a full expression; otherwise the behavior is undefined.

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

Tantalum answered 28/5, 2012 at 10:16 Comment(7)
I mean, c is only modified once, so program can legally print 01 or 10, but not do something weird. Is my understanding correct?Hypophysis
Technically, the behavior is undefined, because there is modification of an object, and accessing it elsewhere without an intervening sequence point. Undefined is not unspecified; it leaves the implementation even more leeway.Kratzer
@JamesKanze: I am not sure but seems you saw the answer prior to my edit.I edited it now to be more specific with standerdese,Please feel free to point out if i missed anything.Actually,your comments are always spot on and needless to say your nod has very high regard for me.Tantalum
@Als Yes. I hadn't seen your edits (although I was reacting to jrok's statement that the program cannot do something weird---it can). Your edited version is good as far as it goes, but in my mind, the key word is partial ordering; sequence points only introduce a partial ordering.Kratzer
The new C++0x standard says essentially the same but in different sections and in different wording :) Quote: (1.9 Program Execution [intro.execution], par 15): "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."Conductivity
I believe there is a bug in this answer. "std::cout<<c++<<c;" cannot translate to "std::operator<<(std::operator<<(std::cout, c++), c)", because std::operator<<(std::ostream&, int) does not exist. Instead, it translates to "std::cout.operator<<(c++).operator(c);", which actually does have a sequence point between the evaluation of "c++" and "c" (an overloaded operator is considered a function call and therefore there is a sequence point when the function call returns). Consequently the behaviour and execution order is specified.Onshore
@ChristopherSmith, no, see James' answer. It doesn't matter that it is a member function call.Ceraceous
K
20

Sequence points only define a partial ordering. In your case, you have (once overload resolution is done):

std::cout.operator<<( a++ ).operator<<( a );

There is a sequence point between the a++ and the first call to std::ostream::operator<<, and there is a sequence point between the second a and the second call to std::ostream::operator<<, but there is no sequence point between a++ and a; the only ordering constraints are that a++ be fully evaluated (including side effects) before the first call to operator<<, and that the second a be fully evaluated before the second call to operator<<. (There are also causual ordering constraints: the second call to operator<< cannot preced the first, since it requires the results of the first as an argument.) §5/4 (C++03) states:

Except where noted, the order of evaluation of operands of individual operators and subexpressions of individual expressions, and the order in which side effects take place, is unspecified. Between the previous and next sequence point a scalar object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be accessed only to determine the value to be stored. The requirements of this paragraph shall be met for each allowable ordering of the subexpressions of a full expression; otherwise the behavior is undefined.

One of the allowable orderings of your expression is a++, a, first call to operator<<, second call to operator<<; this modifies the stored value of a (a++), and accesses it other than to determine the new value (the second a), the behavior is undefined.

Kratzer answered 28/5, 2012 at 11:27 Comment(11)
One catch from your quote of the standard. The "except where noted", IIRC, includes an exception when dealing with an overloaded operator, which treats the operator as a function and therefore creates a sequence point between the first and second call to std::ostream::operator<<(int). Please correct me if I am wrong.Onshore
@ChristopherSmith An overloaded operator behaves like a function call. If c were a user type with a user defined ++, instead of int, the results would be unspecified, but there would be no undefined behavior.Kratzer
If it were "foo(bar(c), c)" I might be inclined to agree it was unspecified, but the expression seems to be the logical equivalent of "foo(foo(bar(c)), c)". That would seem to ensure there is a sequence point between the completion of the call to bar(c) and the outer call to foo. While some optimizers certainly abuse this, I don't think they are in compliance with the language standard.Onshore
@ChristopherSmith Where do you see a sequence point between the two c in foo(foo(bar(c)), c)? There's a sequence point when functions are called, and when they return, but there's no function call required between the evaluations of the two c.Kratzer
Everyone seems pretty sure I'm wrong, so perhaps I am. I see the sequence point because the evaluation of "cout << c++" has to complete to have the "this" pointer to deref for the call to "ostream::operator<<(c)". Though it isn't a virtual function, if it were (and it'd be weird/targic to have special order semantics just for virtual functions), it would seem strictly necessary to complete any work to derive the "this" pointer before invoking the last operator<<.Onshore
@ChristopherSmith cout << c++ is cout.operator<<( c++ ). With a sequence point before the call to std::ostream::operator<<. But the complete expression is cout.operator<<( cout.operator<<( c++ ), c ). With no sequence point between the sub-expressions c++ and c. The only ordering constraints are that the c++ must be complete before the call to << to which it is an argument, and that the first call to << must be complete before the second one. There are no ordering constraints concerning the evaluation of c++ and c.Kratzer
Now I understand what you meant. Again, in the context where "c" is a UDT and you've got an overloaded operator++(int) (which was the "bar" in earlier comment), that would also create a sequence point, no? Only with built in types/operators would there be a way to know what value to pass to the first operator<< call without executing operator++(int).Onshore
@ChristopherSmith If c was a UDT, the overloaded operators would be function calls, and would introduce a sequence point, so the behavior would not be undefined. But it would still be unspecified whether the sub-expression c was evaluated before or after c++, so whether you got the incremented version or not would not be specified (and in theory, wouldn't have to be the same each time).Kratzer
Ah, so my mistake was interpreting how sequence points worked. I thought having a sequence point would require the effective behaviour to be everything before the sequence point happening first. So basically, it is semicolon or bust.Onshore
@ChristopherSmith Everything before the sequence point will happen before anything after the sequence point. But sequence points only define a partial ordering. In the expression in question, for example, there is no sequence point between the sub-expressions c and c++, so the two may occur in any order. As for semicolons... They only cause a sequence point in so far as they are full expressions. Other important sequence points are the function call: f(c++) will see the incremented c in f, and the comma operator, &&, || and ?: also cause sequence points.Kratzer
Thanks for clarifying that. It is subtle as all get out.Onshore
A
4

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.

Ashcraft answered 29/5, 2012 at 21:6 Comment(1)
The question may exhibit a poor programming practice (and even invalid C++). But an answer is supposed to answer the question indicating what's wrong and why it's wrong. A commentary on the question is not an answer even if they are perfectly valid. At best, this can be a comment, not an answer.Wormy

© 2022 - 2024 — McMap. All rights reserved.