Why does rvalue ostream operator<< not use perfect forwarding?

128 Views Asked by At

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.)

0

There are 0 best solutions below