When does operator<< refer to the insertion operator and when to the bitwise left shift?
Asked Answered
N

6

25

When does operator << refer to the insertion operator and when does it refer to the bitwise left shift?

This will output 10, and operator << refers to the left shift.

cout << a.b() << a.a.b << endl;  

And this will output 11, operator << refers to the insertion operator.

cout << a.b();
cout << a.a.b ;

I am confused, when will operator << (when use with cout) refer to the left shift operator?

#include <iostream> 
using namespace std; 

class A { 
public:
    A() { a.a = a.b = 1; }

    struct { int a, b; } a;

    int b(); 
}; 

int A::b(){
    int x=a.a;
    a.a=a.b;
    a.b=x; 
    return x;
};

 int main(){
    A a; 
    a.a.a = 0; 
    a.b(); 

    cout << a.b() << a.a.b << endl;      // ?????
    return 0;
}
Nada answered 10/8, 2016 at 10:17 Comment(14)
By default it's a "bitwise left shift" operator, which works on int like types. This is a built-in facility. If << is overloaded, then it can be used for other purposes.Frisse
IMO this is basic operator precedence and overloading, not worth a question. when does operator << refer to insertion operator and when it refer to bitwise left shift ? (c++) When the operand types, subject to precedence/associativity, clearly tell the language which overload to use.Selfinductance
Possible duplicate of Operator overloadingSelfinductance
Why did you use that horribly formatted mess of code in an attempt to illustrate your point? That doesn't look like an MCVE to me, and it invokes UB as mrtnj pointed out. Thus it distracts from what is just a basic question about operator overloading, precedence, and associativity.Selfinductance
...and turns it into something totally different about order of evaluation. Had the example been an MCVE and written in a way that people could understand more readily, this wouldn't have been a problem.Selfinductance
How did you reach the conclusion that the 10 was the result of bit-shifting?Nebiim
Who is upvoting this? The wrong question was asked, based on the wrong symptoms and a lack of background reading, with an overly confusing and terribly formatted example.Selfinductance
There's only one operator, what it does just depends on how it's overloaded...Nyctaginaceous
@Selfinductance this question made the HNQ list, meaning it will get tons of sympathy upvotes.Edris
correction to my earlier comment, which I'd delete if not for the fact that it holds the 1st bit of the one after it...: Relying upon the order of evaluation in this case invokes unspecified behaviour, not UB. @Snowman Real sympathy would exist in not giving the question false impressions of its own quality, but what can y'do.Selfinductance
@Selfinductance when you have visitors from sites that have nothing to do with programming and don't know what MCVE or duck debugging mean, it happens.Edris
It's shocking that such a poor/confusing question can gain so many upvotes in a few hours and is not closed/deleted.Crestfallen
After the first two snippets, I had to think for a moment if the output had been turned to binary somehow, since that would have explained the 1 << 1 = 10 "result"...Aziza
@Crestfallen Right, and now they've accepted the most basic answer available, which only addresses the question's title, not its horribly confused/confusing body - perhaps because they simply couldn't understand all the better answers that explain why the question's premise was fatally flawed to begin with.Selfinductance
B
16

The problem you are confronted with is not concerning the << operator. In each case, the insertion operator is called.

However, you are faced with a problem concerning the order of evaluation in the command line

cout << a.b() << a.a.b << endl;

The function a.b() has a side effect. It swaps the values a.a.a and a.a.b. Thus, it is evident, wether a.b() is called before or after evaluating the value ov a.a.b.

In C++, the order of evaluation is unspecified, see cppreference.com for a more detailed discussion.

Brodie answered 10/8, 2016 at 10:44 Comment(0)
P
41

This will output 10, and operator<< refer to left shift.

cout << a.b() << a.a.b << endl;

This is caused of the fact that order of evaluation of operands is unspecified. With clang it outputs 11 but with gcc it outputs 10.

Your code:

cout << a.b() << a.a.b << endl;

can be replaced with:

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

clang first evaluates a.b() then a.a.b, g++ does it the other way around. Since your a.b() modifies variables you get different results.

When you rewrite your code as:

cout << a.b();
cout << a.a.b ;

then you have two full expression statements, there is no unspecified behaviour here related to operand evaluation. So you get always the same result.

Profusive answered 10/8, 2016 at 10:36 Comment(1)
Concur. The OP's choice of using strictly 1 and 0 is unfortunate, in that it clouds the eval-order underlying issue.Rate
O
16

In your case all operator <<s are output stream insertion operators because their left argument is of type ostream&, and they group left to right.

The difference in the output is caused by the order of evaluation of function arguments:

cout << a.b() << a.a.b

is

operator<<(operator<<(cout, a.b()), a.a.b)

so the output depends on which of a.a.b or a.b() is evaluated first. This actually unspecified by current standard (C++14) so you could get 11 as well.

AFAIK in C++17 11 will be the only valid output for both cases because it enforces left-to-right evaluation of function parameters.

Update: this seems to be not true, as the committee decided (as of N4606) to go with indeterminately sequenced parameter evaluation mentioned at the bottom of P0145R2. See [expr.call]/5.

Update2: Since we are talking about overloaded operators here, [over.match.oper]/2 in N4606 applies, which says

However, the operands are sequenced in the order prescribed for the built-in operator.

So indeed, the order of evaluaion will be well-defined in C++17. This misunderstanding apparently has been predicted by the authors of P0145:

We do not believe that such a nondeterminism brings any substantial added optimization benefit, but it does perpetuate the confusion and hazards around order of evaluations in function calls

Osmund answered 10/8, 2016 at 10:37 Comment(11)
Is C++ 17 going to specify the order of function parameter evaluation?Lithotomy
@Lithotomy No, Anton is wrong about that. The change is that a.a.b will not be interleaved with operator<<(cout, a.b()). So either operator<<(cout, a.b()) will be fully evaluated before a.a.b, or vice versa. So, for example, the order of evaluation cout then a.a.b then a.b() will no longer be conformant.Luzon
@Luzon Thanks. I think I found the paper for thatLithotomy
The change (one of) is that operator<< becomes a sequence point, even when overloadedRefill
@Luzon In C++17 the order of evaluation of function parameters will be left to right. Quote from the paper NathanOliver linked: Every value computation and side effect associated with the initialization, and the initialization itself, of a parameter is sequenced before every value computation and side effect associated with the initialization of any subsequent parameter. This paper has already been incorporated to the working draft (N4606)Osmund
@AntonSavin Are you sure? Where is the citation that said paper was voted-in verbatim? edit OK, I'll check N4606... Nicol Bolas - a very clued-in C++ user - opened a largely peanut-gallery recent thread on std-proposals thusly: "C++17's CD has apparently voted in the expression evaluation order proposal, but as I understand it, they used the "Alternate Evaluation Order for Function Calls" That says: the expression in the function position is evaluated before all the arguments and the evaluations of the arguments are indeterminately sequenced, but with no interleaving." So, which is it?Selfinductance
@Selfinductance yup they've apparently decided to go with indeterminate sequencing, as of N4606, this is quite unfortunate.Osmund
While they did unfortunately choose indeterminate sequencing for function arguments (and parameter initialization), they also changed the evaluation rules for overloaded operators, which now evaluate their operands as specified by the corresponding built-in operator, not as a function call. Shift operators are now specified to evaluate left-to-right, so that expression will still have a well-defined evaluation order. (By the way, those are member operator functions.) cc @SelfinductanceVentriloquize
@Ventriloquize You are right. The authors of P0145R2 apparently have predicted this - confusion and hazards.Osmund
Well, they created some confusion of their own, by writing the proposal in such a way as to emphasize the variation that they personally preferred, and failing to present the motivating rationale of the alternative. As @Refill pointed out, OP's expression will ultimately be well-defined because of changes to order of evaluation of operands of built-in and overloaded operators, from the same paper.Luzon
@Ventriloquize Excellent clarification, thank you. I won't take a position on the evaluation order of function arguments (because intuition, but then optimisation, but then wheee in circles we go; it's like I'm really being spammed by that reflector thread again)... but making overloaded operators match their built-in ancestors is IMO a great decision - in the sense of wtf, you mean it wasn't always like that? :-)Selfinductance
B
16

The problem you are confronted with is not concerning the << operator. In each case, the insertion operator is called.

However, you are faced with a problem concerning the order of evaluation in the command line

cout << a.b() << a.a.b << endl;

The function a.b() has a side effect. It swaps the values a.a.a and a.a.b. Thus, it is evident, wether a.b() is called before or after evaluating the value ov a.a.b.

In C++, the order of evaluation is unspecified, see cppreference.com for a more detailed discussion.

Brodie answered 10/8, 2016 at 10:44 Comment(0)
Z
9

This call:

cout << a.b() << a.a.b << endl;

will first consider:

cout << a.b()

which correspond to the insertion operator and returns a refence to cout. Thus, the instruction will become:

(returned reference to cout) << a.a.b

which again will call the insertion operator and so on...

If your instruction was:

cout << (a.b() << a.a.b) << endl;

the part between parentheses would be considered first:

a.b() << a.a.b

this time, you have an operator between 2 int: compiler can only resolve it as a call to bitwise operator.

Zelig answered 10/8, 2016 at 10:24 Comment(0)
H
9

Binary operators, such as <<, have two properties that define their usage: (operator) precedence and (left- or right-) associativity. In this case, associativity is the key, and, see e.g. http://en.cppreference.com/w/c/language/operator_precedence, the << operator has left-to-right associativity, so they are sequenced (as if by brackets) from left to right:

((cout << a.b()) << a.a.b) << endl;

or in words sequenced as cout << a.b() then << a.a.b and then << endl.

After this sequencing, operator overloading takes effect on each invocation of << with the given types, which then determines which overload is called and thus if it's a cout-operation or a shift.

Hinch answered 10/8, 2016 at 10:35 Comment(0)
B
4

Without parenthesis, the operands on both sides of the << determine the meaning: int << int == shift, stream << any == insertion. This 'reuse' of the operator may be confusing, indead. But you can solve ambiguities by using parentheses: stream << (int << int) == "int"

Buttress answered 10/8, 2016 at 10:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.