Copying and moving are value-semantic operations. To define them, you first have to decide what properties of a class give its objects distinct values. This point was at first largely sidestepped for the iostreams library, and then C++11 took a different direction incompatible with such a copy constructor.
The state of a stream object comprises two parts: A pointer to a stream buffer with its associated state, and formatting information. Since C++98,
copyfmt expose this information separately.
Since C++11, stream classes also have a
protected interface including a move constructor (and a member called
move) which copies the format but not the stream buffer pointer. This commits iostream to treating the formatting information exclusively as the state of the stream object.
If streams were made copyable at this point, it would only do
copyfmt and not the rest.
The choice to exclude
rdbuf from the value state may be due to the further-muddled value semantics of derived classes such as
std::fstream, which not only expose access to a stream buffer, but also embed and own it.
std::ifstream f( path + filename ); // Owns, or even "is," a file. std::istream i = f; // Observes an externally-managed file. std::istream i2 = i; // OK, copy a shallow reference. std::ifstream f2 = f; // Error, ifstream is more than a shallow reference. std::istream i3 = std::move( f ); // Error? Would retain a reference to an rvalue. std::ifstream f3 = std::move( f ); // OK: full copy including the file buffer.
The semantics could be consistent in some fashion, but it would be a lot of confusion for a moderate gain.