8

Small and pretty nasty problem I've seen several days ago, asked to my friend on interview.

The initial interview question was: "What will be the output of the following code?"

int i = 2;
i = i++ + i++;

The correct answer is ((2 + 2) + 1) + 1 = 6, i.e. post-increment is applied twice before assignment, but after addition.

Then I wanted to create a simple class carrying one integer and overload operator+() and operator++(int) to see in logs the exact order, in which operators will be executed.

This is what I got:

class A
{
public:
A(int _data) : data(_data) { }

A &operator=(const A& _rhs)
{
    data = _rhs.data;
    cout<<" -- assign: "<<data<<endl;
}

A operator++(int _unused)
{
    A _tmp = data;
    data++;

    cout<<" -- post-increment: "<<data<<endl;
    return _tmp;
}

A operator+(const A &_rhs)
{
    A _tmp = data + _rhs.data;

    cout<<" -- addition: "<<data<<"+"<<_rhs.data<<endl;
    return _tmp;
}

inline operator int() const { return data; }

private:
    int data;
};

The result was pretty discouraging:

-- post-increment: 3
-- post-increment: 4
-- addition: 3+2
-- assign: 5

For less sophisticated constructions, such as (A _dt2 = a++; ), it acts as it should, but the order of operators execution is not as for integral types.

It might be compiler specific problem, I guess:

$ gcc --version
gcc (Ubuntu 4.4.3-4ubuntu5) 4.4.3
Copyright (C) 2009 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

So, I'm a bit lost :)

user1016299
  • 83
  • 1
  • 4

4 Answers4

16

The initial interview question was: "What will be the output of the following code?"

int i = 2;
i = i++ + i++;

The correct answer is undefined behaviour, because you're modifying the same variable multiple times without a sequence point in between.

C++03 Standard §5 [expr] p4:

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.

This may not answer your real question, but it will be similar even if you make an integer-like class and overload the operator++(int) and operator+(A const&). The order of evaluation of arguments to a function is unspecified, it may be done in any order the compiler likes, thus the result is unspecified.

Community
  • 1
  • 1
Xeo
  • 129,499
  • 52
  • 291
  • 397
  • If both operators are overloaded, then behaviour is no longer undefined - each operator becomes a function call, which introduces a sequence point. The result is still unspecified though, as you say. – Mike Seymour Oct 27 '11 at 11:32
  • @Mike: Don't just delete your comment and make a new one. :P But yeah, you're right, the joy of operators becoming function calls. – Xeo Oct 27 '11 at 11:35
  • You might as well add the Quote from the Standard, **C++03 Section 5: Expressions, Para 4:** *except where noted [e.g. special rules for && and ||], 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`**.* – Alok Save Oct 27 '11 at 11:40
  • @MikeSeymour,@Xeo: In case of overloading the only _evaluation order_ of "+" operands is unspecified; but it's not UB and _the final result_ of i = i++ + i++ may be well-specified by proper implementation of + and ++ (if the implementation respects monoid properties of numeric + operation and treats ++ as +e returning previous value) – user396672 Oct 27 '11 at 12:29
  • ... sorry, commutativeness of "+" is needed and almost sufficient :) – user396672 Oct 27 '11 at 13:11
7

Aside from what others already pointed out: Taking the title of your question in isolation - "Create C++ integer class to act absolutely identical to integral integer type" - I should point out a completely different reason why it is impossible.

It is (to my knowledge) not possible to emulate the shortcut behaviour of the || and && operators with classes, i.e. both sides of the operand will be evaluated no matter what.

Edit: Check out the comments. "To my knowledge" appears to not have been enough. However, Steve Jessop has a different example that makes the overall point stand valid.

This is completely unrelated to your increment question, but topical to your question title, so I thought it should be mentioned.

DevSolar
  • 67,862
  • 21
  • 134
  • 209
  • 3
    But I don't think you need to reimplement those operators if you want to emulate an integral type. operators `&&` and `||` can automatically invoke your class' `operator bool()`, in which case their short circuiting behavior is conserved. – Fabio A. Oct 27 '11 at 12:14
  • It's true though that it's impossible. A stronger reason why is that implicitly converting an instance of class A to (for example) `long`, "uses up" your 1 user-defined conversion that is permitted in an implicit conversion, whereas converting `int` to `long` doesn't. So if you have another class `B` with a constructor that takes `long`, and `int i = 0; A a = i;`, you'll find that `B b = i;` is allowed but `B b = a;` isn't because the conversion chain A -> int -> long -> B involves two user-defined conversions. – Steve Jessop Oct 27 '11 at 13:39
  • @Fabio A.: That's what you get for making statements from (distant and foggy) memory. I'm sure I've read this somewhere, and 98% sure it was somewhere in Scott Meyer's excellent "Effective C++", but I cannot recall it exactly anymore, and cannot reproduce the effect in test code. To add insult to injury, I don't have access to that book anymore and cannot look it up right now. /me unhappy... but Steve makes another strong point here, so the overall message holds. – DevSolar Oct 27 '11 at 14:25
  • With some expression templates, I suspect it would be possible to short circuit those properly, but it would add a pretty substantial amount of extra work to your implementation. – Dennis Zickefoose Oct 27 '11 at 14:45
  • @SteveJessop But if `A` and `B` are of the same class `FakeInt`, then you're just invoking the copy constructor there. – Fabio A. Oct 28 '11 at 13:29
  • Incidentally, here's a `SafeInt` class that replicates any integer type you throw at it as a template parameter, adding some checks against overflow and division by 0: http://bit.ly/uWX4Lh – Fabio A. Oct 28 '11 at 13:32
  • @Fabio: sure, but what's at question is whether it's possible to imitate the behavior of `int` using class `A`. If `B` *isn't* the same class as `A`, then the behavior of `A` is different from the behavior of `int`. Hence, `A` does not imitate `int` in this respect, since there are plenty of classes in the world with a `long` constructor. – Steve Jessop Oct 28 '11 at 14:20
4

The correct answer is ((2 + 2) + 1) + 1 = 6, i.e. post-increment is applied twice before assignment, but after addition.

That is not the correct answer:

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.
- ISO-IEC-14882

David Schwartz
  • 179,497
  • 17
  • 214
  • 278
1

Actually, you made a pretty bad error early on.

The initial interview question was: "What will be the output of the following code?"

int i = 2;
i = i++ + i++;

The correct answer to this question is "The output is undefined."

By modifying and reading a variable without an interceding sequence point, you are invoking undefined behavior.


More specifically, in this case what bites you in the ass is that the order in which parameters to the + operator are evaluated are undefined; and this is true in the general case of both operators and functions, with a few notable exceptions, namely short-circuiting logical operators.

Williham Totland
  • 28,471
  • 6
  • 52
  • 68