1

Overloads for streaming objects into std::ostream and friends tend to only be written for lvalue ostreams.

Because it can be convenient to write code like std::ofstream("myFile.txt") << "foo";, the standard library is specified (in [ostream.rvalue]) to also provide the following operator<< overload that "translates" streaming into rvalue streams to streaming into lvalue streams:

template<class Ostream, class T> // (eliding SFINAE for readability)
Ostream&& operator<<(Ostream&& os, const T& val)
{
  os << val;
  return std::move(os);
}

See e.g. https://github.com/microsoft/STL/blob/1a418ba4e9c373aee7e9d6ef98efa4c2e2f6b9f4/stl/inc/ostream#L996 for a proper implementation. There is also this discussion and the related LWG issue about preserving most-derived type and/or rvalueness, which have been resolved in C++20 and are not the subject of my question.

Note that the second argument is taken as const ref. I would like to understand why it was specified that way. As it stands, I could readily write an operator<< overload for a move-only type, which will work fine with lvalue streams but fail with rvalue streams:

struct MoveOnly
{
    MoveOnly() = default;
    MoveOnly(const MoveOnly&) = delete;
    MoveOnly(MoveOnly&&) = default;
};

template <class C, class T>
std::basic_ostream<C, T>& operator<<(std::basic_ostream<C, T>&, MoveOnly x);

int main()
{
    // This works fine:
    std::stringstream s1{};
    s1 << MoveOnly{};
    // This does not compile (no suitable overload found):
    std::stringstream() << MoveOnly{};
}

https://godbolt.org/z/1fexfs6Ex

Why does the standard not employ perfect forwarding here? Would there be a problem with something like:

template<class Ostream, class T> // (eliding SFINAE for readability)
Ostream&& operator<<(Ostream&& os, T&& val)
{
  os << std::forward<T>(val);
  return std::move(os);
}

I am writing similar code for my own stream-like class, and am wondering whether there is some disadvantage/problem I'm missing.

(The question of why this always returns the ostream instead of forwarding the inner operator<< result is also on my mind, but the answer to that seems quite clear.)

Max Langhof
  • 23,383
  • 5
  • 39
  • 72
  • 2
    perfect forwarding gives you no advantage here and a `const` parameter guarantees to the caller that the passed in object will not be modified – NathanOliver Aug 04 '22 at 13:20
  • I contest that the linked dupe answers my question. It is certainly not the same question, and no part of the answers covers the const-ness/value category of the second argument. Please take a closer look again (or elaborate how the dupe answers anything about my question). – Max Langhof Aug 04 '22 at 13:32
  • @NathanOliver Sorry, can you elaborate how "my custom operator works with rvalue *and* lvalue ostreams" is "no advantage"? And why is this rvalue wrapper in the business of caring about whether the rhs object is modified - if the inner `operator<<` wants to modify it, let it do so? – Max Langhof Aug 04 '22 at 13:36
  • I was talking about the value to be outputted, not the stream object. There shouldn't be a need for that. convention is that objects that are streamable provide an overload of `operator <<(stream&, const obj_type&)`. If you forward to that nothing is going to change, so there is no need to have a forwarding reference. – NathanOliver Aug 04 '22 at 13:42
  • "*convention is that objects that are streamable provide an overload of* `operator <<(stream&, const obj_type&)`" If you can source that, that sounds like the answer I am looking for! – Max Langhof Aug 04 '22 at 14:01
  • @MaxLanghof It's a convention because that's how the standard library does it for every class type it provides that overload for. – François Andrieux Aug 04 '22 at 14:19
  • @FrançoisAndrieux Nice, I would happily accept that answer. (The supposed dupe notably does not contain any remotely related information. I don't know whether it's appropriate to gold-badge-reopen my own question though.) – Max Langhof Aug 04 '22 at 14:41
  • 1
    @MaxLanghof Best I can find right now is the *Bitshift Operators (used for Stream I/O)* from [here](https://stackoverflow.com/questions/4421706/what-are-the-basic-rules-and-idioms-for-operator-overloading). I've also reopened the Q as I also disagree with it. – NathanOliver Aug 04 '22 at 14:52

0 Answers0