13

I am confused with a question that I saw in a c++ test. The code is here:

#include <iostream>
using namespace std;

class Int {
public:
    int v;
    Int(int a) { v = a; }
    Int &operator[](int x) {
        v+=x;
        return *this;
    }
};
ostream &operator<< (ostream &o, Int &a) {
    return o << a.v;
}

int main() {
    Int i = 2;
    cout << i[0] << i[2]; //why does it print 44 ?
    return 0;
}

I was kinda sure that this would print 24 but instead it prints 44. I would really like someone to clarify this. Is it a cumulative evaluation? Also is the << binary infix ?

Thanks in advance

EDIT: in case of not well defined operator overload, could someone give a better implementation of the overloaded operators here so it would print 24?

BugShotGG
  • 5,008
  • 8
  • 47
  • 63
  • 1
    `cout << i[0] << i[2];` could also be written like this: `operator<<(operator<<(cout, i[0]), i[2]);` – Xaqq May 22 '15 at 23:13
  • 1
    it seems the order of evaluation of two << is not well defined. Debug your code and you will see the i[2] is called first. – Tim3880 May 22 '15 at 23:28
  • I'd really suggest against making `operator[]` have side effects. I think that behavior is going to bite you again at some point and not just here. Imagine how that interacts with multithreading, e.g. –  May 23 '15 at 12:24

2 Answers2

16

This program has indeterminate behavior: the compiler is not required to evaluate i[0] and i[2] in a left-to-right order (the C++ language gives this freedom to compilers in order to allow for optimizations).

For instance, Clang does it in this instance, while GCC does not.

The order of evaluation is unspecified, so you cannot expect a consistent output, not even when running your program repeatedly on a specific machine.

If you wanted to get consistent output, you could rephrase the above as follows (or in some equivalent way):

cout << i[0];
cout << i[2];

As you can see, GCC no longer outputs 44 in this case.

EDIT:

If, for whatever reason, you really really want the expression cout << i[0] << i[2] to print 24, you will have to modify the definition of the overloaded operators (operator [] and operator <<) significantly, because the language intentionally makes it impossible to tell which subexpression (i[0] or [i2]) gets evaluated first.

The only guarantee you get here is that the result of evaluating i[0] is going to be inserted into cout before the result of evaluating i[2], so your best bet is probably to let operator << perform the modification of Int's data member v, rather than operator [].

However, the delta that should be applied to v is passed as an argument to operator [], and you need some way of forwarding it to operator << together with the original Int object. One possibility is to let operator [] return a data structure that contains the delta as well as a reference to the original object:

class Int;

struct Decorator {
    Decorator(Int& i, int v) : i{i}, v{v} { }
    Int& i;
    int v;
};

class Int {
public:
    int v;
    Int(int a) { v = a; }
    Decorator operator[](int x) {
        return {*this, x}; // This is C++11, equivalent to Decorator(*this, x)
    }
};

Now you just need to rewrite operator << so that it accepts a Decorator object, modifies the referenced Int object by applying the stored delta to the v data member, then prints its value:

ostream &operator<< (ostream &o, Decorator const& d) {
    d.i.v += d.v;
    o << d.i.v;
    return o;
}

Here is a live example.

Disclaimer: As others have mentioned, keep in mind that operator [] and operator << are typically non-mutating operations (more precisely, they do not mutate the state of the object which is indexed and stream-inserted, respectively), so you are highly discouraged from writing code like this unless you're just trying to solve some C++ trivia.

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • 4
    @vsoftco: The behavior is not undefined in this case, only indeterminate. Calls to overloaded `operator []` are just regular function calls and they cannot interleave (see paragraph 1.9/15) – Andy Prowl May 22 '15 at 23:52
  • 1
    Hmm, but the par. ends with: *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, and they are not potentially concurrent (1.10), the behavior is undefined.* I am a bit confused, isn't the `operator[]` a "side effect"? Ohh ok ok, I missed the part after the example. Thanks! – vsoftco May 22 '15 at 23:55
  • 1
    @vsoftco: Right, footnote 9 also clarifies a bit: "*In other words, function executions do not interleave with each other*." – Andy Prowl May 22 '15 at 23:58
  • 1
    @AndyProwl Thanks for the answer. I have already tried what you proposed with the 2 `cout` How would we overload the `<<` so it could print `24`? Thanks again, I also made an edit in the original post as well. – BugShotGG May 23 '15 at 00:03
  • 2
    @vsoftco Maybe rewriting the expression as `operator<<(operator<<(cout, i.operator[](0)), i.operator[](2));` will make it easier to understand. The two calls to `operator[]` can happen in any order, but they will not interleave, which makes the modifications to `v` indeterminately sequenced, not unsequenced. – Praetorian May 23 '15 at 00:03
  • @Praetorian Thanks, I understand how the call works, I was just unsure about the function non-interleaving behaviour. – vsoftco May 23 '15 at 00:05
  • @GeoPapas: I'm not sure what you mean. Do you want `cout << i[0] << i[2];` to print `24`? – Andy Prowl May 23 '15 at 00:08
  • @AndyProwl Yes, if that is possible at all. – BugShotGG May 23 '15 at 12:17
5

To explain what's going on here, let's make things much simpler: cout<<2*2+1*1;. What happens first, 2*2 or 1*1? One possible answer is 2*2 should happen first, since it's the leftmost thing. But the C++ standard says: who cares?! After all, the result is 5 either way. But sometimes it matters. For instance, if f and g are two functions, and we do f()+g(), then there is no guarantee which gets called first. If f prints a message, but g exits the program, then the message may never be printed. In your case, i[2] got called before i[0], because C++ thinks it doesn't matter. You have two choices:

One option is to change your code so it doesn't matter. Rewrite your [] operator so that it doesn't change the Int, and returns a fresh Int instead. This is probably a good idea anyway, since that will make it consistent with 99% of all the other [] operators on the planet. It also requires less code:

Int &operator[](int x) { return this->v + x;}.

Your other option is to keep your [] the same, and split your printing into two statements:

cout<<i[0]; cout<<i[2];

Some languages actually do guarantee that in 2*2+1*1, the 2*2 is done first. But not C++.

Edit: I was not as clear as I hoped. Let's try more slowly. There are two ways for C++ to evaluate 2*2+1*1.

Method 1: 2*2+1*1 ---> 4+1*1 ---> 4+1 --->5.

Method 2: 2*2+1*1 ---> 2*2+1 ---> 4+1 --->5.

In both cases we get the same answer.

Let's try this again with a different expression: i[0]+i[2].

Method 1: i[0]+i[2] ---> 2+i[2] ---> 2+4 ---> 6.

Method 2: i[0]+i[2] ---> i[0]+4 ---> 4+4 ---> 8.

We got a different answer, because the [] has side effects, so it matters whether we do i[0] or i[2] first. According to C++, these are both valid answers. Now, we are ready to attack your original problem. As you will soon see, it has almost nothing to do with the << operator.

How does C++ deal with cout << i[0] << i[2]? As before, there are two choices.

Method 1: cout << i[0] << i[2] ---> cout << 2 << i[2] ---> cout << 2 << 4.

Method 2: cout << i[0] << i[2] ---> cout << i[0] << 4 ---> cout << 4 << 4.

The first method will print 24 like you expected. But according to C++, method 2 is equally good, and it will print 44 like you saw. Notice that the trouble happens before the << gets called. There is no way to overload << to prevent this, because by the time << is running, the "damage" has already been done.

Mark VY
  • 1,489
  • 16
  • 31
  • +1 for mentioning how utterly stupid the [] method here is (and other stuff mentioned too!) Because... I've never seen that use before! – Alec Teal May 23 '15 at 10:31
  • @MarkVY hello Mark. Where did you read that c++ doesnt have a priority in operators? `*` operator has higher priority than `+`. In any case, the problem in this code sample is not in the subscript operator `[ ]` implentation but in`<<`. – BugShotGG May 23 '15 at 11:23
  • @GeoPapas I think you misread this answer. C++ does have priority in operators and `*` has higher priority than `+`, this answer assumes you know that already. Perhaps you are mixing up priority with order of evaluation. – M.M May 23 '15 at 12:08
  • `2*2 + 1*1` is a bit of an unclear example though, since in reality the compiler would just substitute `5`. I'd suggest changing it to only use the `f() + g()` example. – M.M May 23 '15 at 12:09