1

I use the stream operator << and the bit shifting operator << in one line. I am a bit confused, why does code A) not produce the same output than code B)?

A)

int i = 4;  
std::cout << i << " " << (i << 1) << std::endl;   //4 8

B)

myint m = 4;
std::cout << m << " " << (m << 1) << std::endl;   //8 8

class myint:

class myint {
    int i;
public:
    myint(int ii) {
        i = ii;
    }
    inline myint operator <<(int n){
        i = i << n;
        return *this;
    }
    inline operator int(){
        return i;
    }
};

thanks in advance
Oops

OlimilOops
  • 6,747
  • 6
  • 26
  • 36
  • Almost a duplicate of: http://stackoverflow.com/questions/2603312/the-result-of-int-c0-coutcc/. For most practical purposes they're the same, even though that used "++' instead of "<<" as the operator that did the modification. – Jerry Coffin Apr 21 '10 at 21:19
  • 2
    @Jerry Coffin: they are quite similar but there is the whole << vs << confusion and the fact the << doesn't conventionally change its arguments. – CB Bailey Apr 21 '10 at 21:23

7 Answers7

8

Your second example is undefined behavior.

You have defined the << operator on your myint class as if it were actually <<=. When you execute i << 1, the value in i is not modified, but when you execute m << 1, the value in m is modified.

In C++, it is undefined behavior to both read and write (or write more than once) to a variable without an intervening sequence point, which function calls and operators are not, with respect to their arguments. It is nondeterministic whether the code

std::cout << m << " " << (m << 1) << std::endl;  

will output the first m before or after m is updated by m << 1. In fact, your code may do something totally bizarre, or crash. Undefined behavior can lead to literally anything, so avoid it.

One of the proper ways to define the << operator for myint is:

myint operator<< (int n) const
{
   return myint(this->i << n);
}

(the this-> is not strictly necessary, just my style when I overload operators)

Tyler McHenry
  • 74,820
  • 18
  • 121
  • 166
  • 1
    But overloaded operators are function calls. There are *lots* of sequence points. – CB Bailey Apr 21 '10 at 21:20
  • 2
    `std::cout << m << " " << (m << 1)` is equivalent to `std::cout.operator<<(m).operator<<(" ").operator<<(m.operator<<(1))`. There is a sequence point between when each of the outermost operators are invoked, but there is no sequence point between the evaluations of the arguments that are passed to the operators, including the inner call to `operator<<`. In other words, in `f(a()).g(b())`, there is a sequence point between the call to `f` and the call to `g`, but the order of calls to `a` and `b` is undefined. – Tyler McHenry Apr 21 '10 at 21:23
  • Perhaps you should update your answer because without the explanation in your comment, it's far from obvious that none of the many sequence points don't guarantee that there isn't one between the crucial expressions. – CB Bailey Apr 21 '10 at 21:29
  • Yep, guaranteed by §1.9/17, as the result of each call provides an argument to the next. – Potatoswatter Apr 21 '10 at 21:30
  • @Charles I updated the answer to say that "function calls and operators are not sequence points *with respect to their arguments* " If you've got a clearer way to say it without going into too long a digression on sequence points, feel free to edit my answer. – Tyler McHenry Apr 21 '10 at 21:31
  • 2
    @Tyler: strictly speaking it's not undefined behavior, it's unspecified. There's subtle difference (that's not particularly useful here), but one thing that is different is that the compiler is not allowed to produce 'crashing' code. The compiler must produce some ordering of the operations, even if that ordering might be different from one execution to the next. – Michael Burr Apr 21 '10 at 21:33
  • @Tyler McHenry: I know what you're saying, but I think it sounds at least a little misleading because there *is* a sequence pointer between the evaluation of a function's parameters and the execution of its body which superficially sounds like it should help. – CB Bailey Apr 21 '10 at 21:35
  • @Michael Burr: Oh no, here we go again ;) . – CB Bailey Apr 21 '10 at 21:37
  • It's actually undefined. §5/4: "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**." – Tyler McHenry Apr 21 '10 at 23:30
  • Now, granted, any sane compiler would not crash on the example given in the OP, but it is UB, so in theory anything is possible. – Tyler McHenry Apr 21 '10 at 23:31
5

Because int << X returns a new int. myint << X modifies the current myint. Your myint << operator should be fixed to do the former.

The reason you're getting 8 for the first is that apparently m << 1 is called first in your implementation. The implementation is free to do them in any order.

Edward Strange
  • 40,307
  • 7
  • 73
  • 125
2

Your << operator is in fact a <<= operator. If you replace the line with

std::cout << i << " " << (i <<= 1) << std::endl;   //8 8

you should get 8 8.

Neil G
  • 32,138
  • 39
  • 156
  • 257
2

since m is a myInt your second example could be rewritten as:

std::cout << m << " " << (m.operator<<( 1)) << std::endl; 

The order of evaluation for the subexpressions m and (m.operator<<( 1)) is unspecified, so there's no saying which "m" you'll get for the 1'st expression that m is used in (which is a simple m expression). So you might get a result of "4 8" or you might get "8 8".

Note that the statement doesn't result in undefined behavior because there are sequence points (at least one function call) between when m is modified and when it's 'read'. But the order of evaluation of the subexpressions is unspecified, so while the compiler has to produce a result (it can't crash - at least not legitimately), there's no saying which of the two possible results it should produce.

So the statement is about as useful as one that has undefined behavior, which is to say it's not very useful.

Michael Burr
  • 333,147
  • 50
  • 533
  • 760
1

Well (m << 1) is evaluated before m and therefore m holds 8 already, as in your operator<< you overwrite your own value.

This is wrong behaviour on your side, the operator<< should be const and not change your object.

ypnos
  • 50,202
  • 14
  • 95
  • 141
0

Because the << operator of myint modifies its lhs. So after evaluating m << 1, m will actually have the value 8 (while i << 1 only returns 8, but does not make i equal to 8). Since it is not specified whether m<<1 executes before cout << m (because it's not specified in which order the arguments of a function or operator are evaluated), it is not specified whether the output will be 8 8 or 4 8.

sepp2k
  • 363,768
  • 54
  • 674
  • 675
0

The C++ language does not define the order of evaluation of operators. It only defines their associativity.

Since your results depend on when the operator<< function is evaluated within the expression, the results are undefined.

Algebraic operator $ functions should always be const and return a new object:

inline myint operator <<(int n) const { // ensure that "this" doesn't change
    return i << n; // implicit conversion: call myint::myint(int)
}
Potatoswatter
  • 134,909
  • 25
  • 265
  • 421