Why Can't Std::Ostream Be Moved

Why can't std::ostream be moved?

Originally they were movable. This turned out to be a design flaw on my part, and discovered by Alberto Ganesh Barbati:

http://cplusplus.github.io/LWG/lwg-defects.html#911

The issue shows a few examples where ostream gets moved and/or swapped, and the results are surprising, instead of expected. I was convinced that these types should not be publicly movable nor swappable by this issue.

How can an std::ostream be moved?

You've almost got it right. Your example is move constructing the ios base twice. You should move only the direct base class. And assuming there is member streambuf, move that too:

class omstream
: public std::ostream {
// suitable members
public:
omstream(/* suitable constructor arguments */);
omstream(omstream&& other) // follow recipe of 27.9.1.11 [ofstream.cons] paragraph 4
: std: ostream(std::move(other)),
// move any members {
this->set_rdbuf(/* install the stream buffer */);
}
// other helpful or necessary members
};

I changed "get" to "install" in the set_rdbuf comment. Typically this installs a pointer to the member streambuf into the ios base class.

The current unorthodox design of the move and swap members of istream/ostream was set up to make the move and swap members of the derived classes (such as ofstream and omstream) more intuitive. The recipe is:

Move the base and members, and in the move constructor set the rdbuf.

It is that embedded rdbuf that is the complicating factor for the entire hierarchy.

Why can't I move std::ofstream?

According to the standard

27.9.1.11 basic_ofstream constructors

or, its more "readable" version http://en.cppreference.com/w/cpp/io/basic_ofstream/basic_ofstream , std::basic_ostream<> has a move constructor, so the code should compile.

clang++ 3.5 compiles it with -std=c++11 or -std=c++1y. Also gcc5 compiles it, so probably it is not implemented in libstdc++ for gcc < 5

Interestingly, the lack of move semantics is not mentioned on gcc's stdlibc++ implementation https://gcc.gnu.org/onlinedocs/libstdc++/manual/status.html#status.iso.2014

See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54316 for a bug report, thanks to @BoBTFish for pointing out. It is confirmed that the issue was fixed in gcc5.

Weird behaviour when holding std::ostream rvalue member

The temporary std::ofstream{"testic"} that you created only exists for the duration of the constructor call. After that it is destroyed and the file is closed, which means you are left with a reference that refers to garbage. Using that reference results in undefined behavior.

To fix it you can remove the reference all together (the && from std::ostream&& out and std::ostream&& o) and have it create a new object that is initialized from the temporary.

The above won't work because std::ostream cannot be moved. You will have to use a pointer instead if you want to maintain polymorphism. If that isn't important you can change all std::ostream&& to std::ofstream:

class A {
private:
std::unique_ptr<std::ostream> out;

public:
A(std::unique_ptr<std::ostream> o) : out(std::move(o)) {
out->write("test", 4);
}

void writeTest2() {
out->write("test2", 5);
out->flush();
}
};

int main() {
A a{std::make_unique<std::ofstream>("testic")};
a.writeTest2();
}

Can't we manage std::mapstring,ofstream?

As an std::ostream is not copyable (copy constructor and assignment operator are marked deleted), you have to either construct the ofstream directly in the map (e.g. using std::map::emplace()) or use move assignment.

Construct in-place

There are basically two ways, either default-construct stream in the map (pre C++11), or call std::map::emplace() to supply ofstream constructor arguments.

Using default-construction (works even pre C++11):

map<string,ofstream> m;

// default-construct stream in map
ofstream& strm = m["test"];
strm.open("test_output");
strm << "foo";

Using emplacement:

// 1st parameter is map key, 2nd parameter is ofstream constructor parameter    
auto res = m.emplace("test", "test_output");
auto& strm = res.first->second;
strm << "bar";

Move assignment

We can construct the stream outside of the map first, turn it into an rvalue by calling std::move() and use move assignment operator to move it into the map:

map<string,ofstream> m;    
ofstream strm("test_output");
m["test"] = std::move( strm );
// strm is not "valid" anymore, it has been moved into the map

We can even get rid of std::move() if we directly create the stream as an rvalue:

m["test"] = ofstream("test_output");

Move assignment is less efficient than the other methods, because first a stream will be default-constructed in the map, just to be replaced by the move-assigned stream then.

Live demo of all three methods.

Note: Sample code omitts any error handling for brevity. State of stream should be checked after opening and after each stream operation.

Why the constructor of std::ostream is protected?

I'll admit that I don't understand it either. I can't find any
default constructor at all for std::istream, and I would think
you would want one if you want to create a bidirectional stream,
because of the strange way std::ios_base works: the
constructor does not initialize anything, but the derived
class must call std::ios_base::init explicitly in its
constructor. When multiple inheritance is involved (i.e.
bidirectional IO, where the class derives from both
std::istream and std::ostream), I would expect only the most
derived class to call std::ios_base::init. (In
std::iostream, std::ios_base::init will be called twice.)
In fact, before looking it up in the standard, I was about to
answer that the default constructor was protected, because it
didn't call std::ios_base::init, and using it directly, rather
than in a derived class, would result in an uninitialized
stream.

Anyhow, your immediate problem has a simple solution:

std::ostream out( NULL );

Also: the function you need to set up its sink later is the
non-const version of rdbuf(), not copyfmt(). rdbuf() is
used to read and to set the pointer to the streambuf,
copyfmt() copies the formatting flags, but does not touch
the pointer to streambuf.

So you can do things like:

std::ostream out( NULL );
// ...
std::filebuf fileBuffer;
if ( filenameGiven ) {
fileBuffer.open( filename.c_str(), std::ios_base::out );
}
if ( fileIsOpen() ) {
out.rdbuf( &fileBuffer );
} else {
out.rdbuf( std::cout.rdbuf() );
}

(I do this a lot. In fact, I thought that it was the usual
idiom when you didn't know up front whether to output to a file
or to std::cout.)

EDIT:

And yet another correction: the non-const version of rdbuf calls clear(),
so you don't have to. (I knew I'd done this without calling clear(), but
when I saw that init set badbit...)

Anyhow: the summary is: it's usually preferrable to pass a pointer to a valid
streambuf to the constructor of std::ostream, but if you can't, it's
perfectly valid to pass a null pointer, and set a valid pointer later using
rdbuf(). And the answers which say otherwise are simply wrong.

Why does std::ostream not compile when used in ternary operator?

The standard contains some complicated rules regarding how the conditional expression is evaluated ([expr.cond]). But instead of quoting those rules here, I'm going to explain how you should think about them.

The result of the conditional expression can be an lvalue, xvalue, or prvalue. But which one of these it is has to be known at compile time. (The value category of an expression can never depend on what happens at runtime). It's easy to see that if both the second and third expressions are lvalues of the same type, then the result can also be made an lvalue, and no copying has to occur. If both the second and third expressions are prvalues of the same type, then, as of C++17, no copying has to occur either---a prvalue of type T represents the deferred initialization of an object of type T, and the compiler simply chooses, based on the condition, which of those two prvalues gets passed on to eventually be used to initialize an object.

But when one expression is an lvalue and the other is a prvalue of the same type, then the result must be a prvalue. If the standard said the result would be an lvalue, that would be illogical, as the condition may cause the prvalue operand to be selected, and you can't convert a prvalue into an lvalue. But you can do it the other way around. So the standard says that when one operand is an lvalue and the other is a prvalue of the same type, then the lvalue must undergo the lvalue-to-rvalue conversion. And if you attempt an lvalue-to-rvalue conversion on an std::ostream object, the program will be ill-formed since the copy constructor is deleted.

Thus:

  • In A, both operands are prvalues so there is no lvalue-to-rvalue conversion; this is ok in C++17 (but not in C++14).
  • In B, the lvalue-to-rvalue conversion is needed for o, so this won't compile.
  • In C, oRef is an lvalue so the lvalue-to-rvalue conversion is still required, so this also won't compile.
  • In D, oRRef is still an lvalue (as the name of an rvalue reference is an lvalue).
  • In E, one argument is a prvalue and one is an xvalue. The xvalue still needs to be converted into a prvalue to make the result a prvalue.

The case of E deserves some further remarks. In C++11 it was clear that if one argument is an xvalue and the other is a prvalue of the same type, the xvalue must undergo the (misleadingly named) lvalue-to-rvalue conversion to yield a prvalue. In the case of std::ostream, this uses the protected move constructor (so the program violates member access control). In C++17, one could contemplate changing the rule so that instead of the xvalue being converted to a prvalue, the prvalue gets materialized to yield an xvalue instead, obviating the need for a move. But this change has no obvious benefit and it's questionable whether it's the most reasonable behaviour, so that's probably why it wasn't made (if it were even considered).

How to move std::ostringstream's underlying string object?

The standard says that std::ostringstream::str() returns a copy.

One way to avoid this copy is to implement another std::streambuf derived-class that exposes the string buffer directly. Boost.IOStreams makes this pretty trivial:

#include <boost/iostreams/stream_buffer.hpp>
#include <iostream>
#include <string>

namespace io = boost::iostreams;

struct StringSink
{
std::string string;

using char_type = char;
using category = io::sink_tag;

std::streamsize write(char const* s, std::streamsize n) {
string.append(s, n);
return n;
}
};

template<typename T>
std::string ToString(T const& obj) {
io::stream_buffer<StringSink> buffer{{}};

std::ostream stream(&buffer);
stream << obj;
stream.flush();

return std::move(buffer->string); // <--- Access the string buffer directly here and move it.
}

int main() {
std::cout << ToString(3.14) << '\n';
}


Related Topics



Leave a reply



Submit