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.
CodePudding user response:
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;
.
CodePudding user response:
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.