25

§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?

Xeo
  • 129,499
  • 52
  • 291
  • 397
  • for some reason all MSVCs I've seen seem to allow one to bind rvalues to a non-const reference so returning rvalue wont change anything for MSVS. – Terenty Rezman Jul 25 '13 at 13:23

1 Answers1

16

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.

T.C.
  • 133,968
  • 17
  • 288
  • 421
Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • 3
    Hm.. then.. wouldn't it be even better to return no reference at all but just a `basic_ostream` that is moved-into? That would disable lvalue ref binding and allow proper semantics of lifetime extension through `auto &&` and `auto const&`, while still being able to chain that operator like you proposed. – Xeo Jan 12 '12 at 01:38
  • That would be safer. But I'm not sure the performance hit would be worth it. Though moving a stream is relatively cheap, I'm not sure it is cheap enough to do on every chaining operation. But it is something to think more about... – Howard Hinnant Jan 12 '12 at 15:42
  • I believe that optimizations should kick in here, eliding nearly all moves (though it may be bad to rely on that). I noticed another small drawback however - user-defined derived classes of `std::ostream` will also need to be movable, and there may be those that contain a fixed-size buffer that would take O(N) time to "move". Again, optimizations will most likely make this a no-op, but if they don't kick in, it's more severe. – Xeo Jan 12 '12 at 15:50