简体   繁体   中英

Can we overload operator<< with the first parameter being of the type std::ostream&& instead of std::ostream&

I have learnt that we can overload operator<< as shown below:

class Person 
{
    public:
        friend std::ostream& operator<<(std::ostream& os, const Person& obj);
};

I absolutely understand the reason for the type of the parameters being reference. For example, the first parameter is a reference because streams can't be copied and the second parameter is a reference because we want to reflect the changes(if any as in case of operator>> ) made to the original object. I know that since we have a low-level const in the second parameter its state can't be changed and by using reference we avoid copying.

My question is that can we( and should we) use rvalue reference instead of lvalue reference for the first parameter as shown below:

friend std::ostream& operator<<(std::ostream&& os, const Person& obj); //note the first parameter is rvalue-reference

Is there any reason why we should/shouldn't do what is shown above? More importantly, what will happen if we do so.

Similarly, my second question is that can we make the return type to be std::ostream&& instead of std::ostream& . In this case what will happen/change.

PS: I am learning C++ and have asked this question out of curiosity. That is, to deepen my knowledge of references and overloading.

If you tried writing something like the following, the program would not compile (this was already mentioned in the comments by @PaulMcKenzie):

#include <iostream>
#include <ostream>

struct Person {
        int x = 0;
};

std::ostream& operator<<(std::ostream&& os, const Person& obj)
{
    return os << obj.x;
}

int main()
{
    Person p;
    std::cout << p;
}

You could however make the program compile by changing the last line in main into

std::move(std::cout) << p; // don't do this in real code

Then, the program would compile and output 0 . However the std::cout stream will be in a moved-from state after that and should not be used anymore.

Regarding some of the comments, there seems to be a misunderstanding about what a r-value reference is. Some seem to think it is just a better l-value reference, that we had no access to before C++11.

L-value references refer to something, that can be used on the left hand side of an expression. You can think of it as an reference to an actual object, that can be changed and assigned to and so forth. An r-value reference on the other hand refers to a temporary object, something, that can be used on the right hand side of an assignment.

Suppose you had two overloads of the operator<< with the following signature.

std::ostream& operator<<(std::ostream& os, const Person& obj);
std::ostream& operator<<(std::ostream&& os, const Person& obj);

The rules are, that the first accepts either a l-value or a r-value reference. The latter accepts only r-value references. If we call the operator with a l-value ( std::cout is an example to this), the first overload will be used. If we call the operator with an r-value (like with std::move(std::cout) ) the second will be used. You can omit the r-value reference overload, in wich case the first one is selected all the time, but you cannot omit the first one if you want to call the operator with a l-value . However calling the operator with an l-value is the thing you almost always want:

std::cout << p;  // will not compile if there is only an r-value reference overload

Now what are the use cases of r-value references? They are used to implement move semantics on objects. R-value references are used to refer to objects, that need no longer be valid after the current statement (they have no durability). While you can theoretically move the std::cout stream, just like shown above, this is not useful at all. Streams are meant to be used as objects with long livetime and not as temporaries.

So to finally answer your question : You should not do it, because streams are usually not used as temporary objects. If you really want to provide a r-value reference overload to the << operator, go ahead, but also provide a l-value overload for the normal use case of std::cout << p; .

My question is that can we( and should we) use rvalue reference instead of lvalue reference for the first parameter as shown below:

friend std::ostream& operator<<(std::ostream&& os, const Person& obj); //note the first parameter is rvalue-reference

This is technically possible (with some limitations) but is definitely very strange thing to do. Using rvalue reference usually implies the object will be “consumed” (eg moved-from) and shouldn't be used in subsequent operations. But writing to a stream doesn't consume it, generally; the stream is still available for subsequent writes.

There are no technical reasons why you can't do either of those. However, if you do, you aren't interoperating with all the other things that define ostream operators.

If the input is ostream && then you can't have an lvalue stream to the left

Person p;
std::cout << p // can't bind std::cout to ostream&&

It's bad manners to return an rvalue reference to something that you received by lvalue reference.

std::ostream&& operator<<(std::ostream& os, const Person& obj) {
    // something involving obj
    return std::move(os); // stealing something that shouldn't be stolen!
}

If you want to have an expression with a temporary ostream , you can. The standard library defines a template that takes an rvalue stream, applies the equivalent lvalue stream operation, and move-returns that stream.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM