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

R Sahu
  • 204,454
  • 14
  • 159
  • 270
pravs
  • 1,081
  • 2
  • 8
  • 8
  • 30
    Did you ask for an explanation? I often interview potential candidates and am quite interested in receiving questions, it shows interest. – Brady May 28 '12 at 10:14
  • Exactly, did you get an answer out from the interviewer? He is absolutely wrong. The output is always 01. – Ashwin May 28 '12 at 10:20
  • 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. – pravs May 28 '12 at 10:22
  • @Ashwinkumar It could be either 01 or 10. – jrok May 28 '12 at 10:32
  • @jrok Agreed.. It could be 01 or 10 or undefined.. Understanding how.. – Ashwin May 28 '12 at 10:36
  • 3
    @jrok It's undefined behavior. Anything the implementation does (including sending an insulting email in your name to your boss) is conformant. – James Kanze May 28 '12 at 11:12
  • 2
    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. – CB Bailey May 28 '12 at 11:34
  • 3
    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. – leftaroundabout May 28 '12 at 11:38
  • @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. – Ben Voigt May 28 '12 at 14:54
  • @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. – CB Bailey May 28 '12 at 15:13
  • 2
    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. :) – tchrist May 28 '12 at 19:36
  • Out of curiosity, why is alternative a `10`? Shouldn't it rather be `00`? – HelloGoodbye May 22 '17 at 15:30
  • 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. – HelloGoodbye May 22 '17 at 15:34

4 Answers4

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.

R Sahu
  • 204,454
  • 14
  • 159
  • 270
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
  • @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 01 – pravs May 28 '12 at 11:26
  • @Maxim Not that it makes a different, but since `c` has type `int`, the `operator<<` here are member functions. – James Kanze May 28 '12 at 11:32
  • 2
    @pravs: whether `operator<<` is a member function or a free-standing function doesn't affect sequence points. – Maxim Egorushkin May 28 '12 at 11:39
  • 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! – pravs May 28 '12 at 11:52
  • 11
    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. – Rafał Dowgird May 28 '12 at 14:01
  • 2
    `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](http://stackoverflow.com/a/10783921). – Deduplicator May 31 '14 at 00:45
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].

R Sahu
  • 204,454
  • 14
  • 159
  • 270
Alok Save
  • 202,538
  • 53
  • 430
  • 533
  • I mean, `c` is only modified once, so program can legally print 01 or 10, but not do something weird. Is my understanding correct? – jrok May 28 '12 at 10:40
  • 1
    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. – James Kanze May 28 '12 at 11:14
  • @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. – Alok Save May 28 '12 at 11:18
  • 1
    @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. – James Kanze May 28 '12 at 11:30
  • 4
    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."* – Rafał Dowgird May 28 '12 at 14:41
  • 2
    I believe there is a bug in this answer. "std::cout< – Christopher Smith Sep 21 '13 at 04:08
  • @ChristopherSmith, no, see [James' answer](http://stackoverflow.com/a/10783921/1212596). It doesn't matter that it is a member function call. – Paul Draper Oct 14 '14 at 18:23
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.

Community
  • 1
  • 1
James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • 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. – Christopher Smith Sep 21 '13 at 03:14
  • @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. – James Kanze Sep 21 '13 at 10:28
  • 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. – Christopher Smith May 01 '14 at 20:11
  • 1
    @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`. – James Kanze May 02 '14 at 08:21
  • 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<<. – Christopher Smith Dec 30 '14 at 09:43
  • @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`. – James Kanze Dec 30 '14 at 18:25
  • 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). – Christopher Smith Dec 30 '14 at 19:58
  • 1
    @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). – James Kanze Dec 30 '14 at 20:18
  • 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. – Christopher Smith Dec 30 '14 at 22:06
  • 1
    @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. – James Kanze Jan 04 '15 at 17:40
  • Thanks for clarifying that. It is subtle as all get out. – Christopher Smith Jan 10 '15 at 02:05
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.

Paul Marrington
  • 557
  • 2
  • 7
  • 4
    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. – P.P Aug 19 '13 at 09:04