911. I/O streams and move/swap semantic

Section: 30.7.4 [input.streams], 30.7.5 [output.streams] Status: C++11 Submitter: Alberto Ganesh Barbati Opened: 2008-09-29 Last modified: 2016-02-10

Priority: Not Prioritized

View all issues with C++11 status.

Discussion:

Class template basic_istream, basic_ostream and basic_iostream implements public move constructors, move assignment operators and swap method and free functions. This might induce both the user and the compiler to think that those types are MoveConstructible, MoveAssignable and Swappable. However, those class templates fail to fulfill the user expectations. For example:

std::ostream os(std::ofstream("file.txt"));
assert(os.rdbuf() == 0); // buffer object is not moved to os, file.txt has been closed

std::vector<std::ostream> v;
v.push_back(std::ofstream("file.txt"));
v.reserve(100); // causes reallocation
assert(v[0].rdbuf() == 0); // file.txt has been closed!

std::ostream&& os1 = std::ofstream("file1.txt");
os1 = std::ofstream("file2.txt");
os1 << "hello, world"; // still writes to file1.txt, not to file2.txt!

std::ostream&& os1 = std::ofstream("file1.txt");
std::ostream&& os2 = std::ofstream("file2.txt");
std::swap(os1, os2);
os1 << "hello, world"; // writes to file1.txt, not to file2.txt!

This is because the move constructor, the move assignment operator and swap are all implemented through calls to std::basic_ios member functions move() and swap() that do not move nor swap the controlled stream buffers. That can't happen because the stream buffers may have different types.

Notice that for basic_streambuf, the member function swap() is protected. I believe that is correct and all of basic_istream, basic_ostream, basic_iostream should do the same as the move ctor, move assignment operator and swap member function are needed by the derived fstreams and stringstreams template. The free swap functions for basic_(i|o|io)stream templates should be removed for the same reason.

[ Batavia (2009-05): ]

We note that the rvalue swap functions have already been removed.

Bill is unsure about making the affected functions protected; he believes they may need to be public.

We are also unsure about removing the lvalue swap functions as proposed.

Move to Open.

[ 2009-07 Frankfurt: ]

It's not clear that the use case is compelling.

Howard: This needs to be implemented and tested.

[ 2009-07-26 Howard adds: ]

I started out thinking I would recommend NAD for this one. I've turned around to agree with the proposed resolution (which I've updated to the current draft). I did not fully understand Ganesh's rationale, and attempt to describe my improved understanding below.

The move constructor, move assignment operator, and swap function are different for basic_istream, basic_ostream and basic_iostream than other classes. A timely conversation with Daniel reminded me of this long forgotten fact. These members are sufficiently different that they would be extremely confusing to use in general, but they are very much needed for derived clients.

The reason for this behavior is that for the std-derived classes (stringstreams, filestreams), the rdbuf pointer points back into the class itself (self referencing). It can't be swapped or moved. But this fact isn't born out at the stream level. Rather it is born out at the fstream/sstream level. And the lower levels just need to deal with that fact by not messing around with the rdbuf pointer which is stored down at the lower levels.

In a nutshell, it is very confusing for all of those who are not so intimately related with streams that they've implemented them. And it is even fairly confusing for some of those who have (including myself). I do not think it is safe to swap or move istreams or ostreams because this will (by necessary design) separate stream state from streambuffer state. Derived classes (such as fstream and stringstream must be used to keep the stream state and stream buffer consistently packaged as one unit during a move or swap.

I've implemented this proposal and am living with it day to day.

[ 2009 Santa Cruz: ]

Leave Open. Pablo expected to propose alternative wording which would rename move construction, move assignment and swap, and may or may not make them protected. This will impact issue 900.

[ 2010 Pittsburgh: ]

Moved to Ready for Pittsburgh.

Proposed resolution:

30.7.4.1 [istream]: make the following member functions protected:

basic_istream(basic_istream&&  rhs);
basic_istream&  operator=(basic_istream&&  rhs);
void  swap(basic_istream&  rhs);

Ditto: remove the swap free function signature

// swap: 
template <class charT, class traits> 
  void swap(basic_istream<charT, traits>& x, basic_istream<charT, traits>& y);

30.7.4.1.2 [istream.assign]: remove paragraph 4

template <class charT, class traits> 
  void swap(basic_istream<charT, traits>& x, basic_istream<charT, traits>& y);

Effects: x.swap(y).

30.7.4.6 [iostreamclass]: make the following member function protected:

basic_iostream(basic_iostream&&  rhs);
basic_iostream&  operator=(basic_iostream&&  rhs);
void  swap(basic_iostream&  rhs);

Ditto: remove the swap free function signature

template <class charT, class traits> 
  void swap(basic_iostream<charT, traits>& x, basic_iostream<charT, traits>& y);

30.7.4.6.3 [iostream.assign]: remove paragraph 3

template <class charT, class traits> 
  void swap(basic_iostream<charT, traits>& x, basic_iostream<charT, traits>& y);

Effects: x.swap(y).

30.7.5.1 [ostream]: make the following member function protected:

basic_ostream(basic_ostream&&  rhs);
basic_ostream&  operator=(basic_ostream&&  rhs);
void  swap(basic_ostream&  rhs);

Ditto: remove the swap free function signature

// swap: 
template <class charT, class traits> 
  void swap(basic_ostream<charT, traits>& x, basic_ostream<charT, traits>& y);

30.7.5.1.2 [ostream.assign]: remove paragraph 4

template <class charT, class traits> 
  void swap(basic_ostream<charT, traits>& x, basic_ostream<charT, traits>& y);

Effects: x.swap(y).