§27.7.3.9
defines the following overload for operator<<
:
template <class charT, class traits, class T> basic_ostream<charT, traits>& operator<<(basic_ostream<charT, traits>&& os, const T& x);
Effects:
os << x
Returns:os
(§27.7.2.6
defines the rvalue overload for operator>>
.)
Basically, it just forwards to an lvalue overload. I consider this overload to be pretty dangerous (the istream
one even more so than the ostream
one, actually), consider the following:
#include <sstream> #include <iostream> int main(){ auto& s = (std::stringstream() << "hi there!\n"); std::cout << s.rdbuf(); // oops }
Live example on Ideone (perfect example of undefined behaviour. Prints nothing for me on MSVC10).
The above example might look contrived, but it shouldn't be too hard to get into this situation in generic code or when passing the (std::stringstream() << "text")
to a function that provides an lvalue and an rvalue overload and stores the std::ostream
or std::istream
in different ways according to the overload.
Now, what would be an argument agains returning a basic_ostream<charT, traits>&&
and specifying the following?
Returns: move(os)
(And the same for basic_istream
.)
Is there anything I'm overlooking? At the current state, in my eyes, it just looks dangerous and like a defect. I skimmed through the LWG issue list and found this proposal (hi @HowardHinnant!). It indeed returns an rvalue, however only for the added benefit of being able to chain this special operator, not specifically addressing the safety issue I described above (though it certainly does solve it). Additionally, it's marked as closed and for reconsideration for the next standard. As such, I thought I'd ask here:
Is there any good reason why the above mentioned overload returns an lvalue reference?
An lvalue is an expression that yields an object reference, such as a variable name, an array subscript reference, a dereferenced pointer, or a function call that returns a reference. An lvalue always has a defined region of storage, so you can take its address. An rvalue is an expression that is not an lvalue.
By overloading a function to take a const lvalue reference or an rvalue reference, you can write code that distinguishes between non-modifiable objects (lvalues) and modifiable temporary values (rvalues). You can pass an object to a function that takes an rvalue reference unless the object is marked as const .
By default, the compiler cannot bind a non-const or volatile lvalue reference to an rvalue.
Rvalue references allow programmers to avoid logically unnecessary copying and to provide perfect forwarding functions. They are primarily meant to aid in the design of higer performance and more robust libraries.
It's a defect and it is my fault, sorry. LWG 1203 (thanks for finding that for me! :-)) is my current opinion of the correct fix for the "rvalue-stream-inserter". Note though that you could still catch it and be in trouble:
auto&& s = (std::stringstream() << "hi there!\n"); std::cout << s.rdbuf(); // oops
Though at least in the above code it is a little more obvious (because of the &&
) that you're doing something you shouldn't.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With