Doc. no.: | N2186=07-0046 |
Date: | 2007-03-06 |
Author: | James Kanze |
email: | james.kanze@gmail.com |
In practice, in most if not all implementations of the standard library, the header <iostream> includes <istream> and <ostream>, and probably some other files. Many programmers have grown used to this, and aren't even aware that it isn't standard. Many books also just include <iostream>, and suppose that the << and >> operators will be available. Regardless of the original intent of the committee, or even my personal preferences (which would be for a very lightweight <iostream>), vendors will continue to have <iostream> include <istream> and <ostream>, because that's what users expect, and users will continue to believe that this is standard, because that's what all of the vendors do. In this case, it is probably best if the standard just align itself with reality, and require what everyone already does.
The headers <istream> and <ostream>, obviously. In practice, it's hard to imagine an implementation in which these didn't include <ios>, and most people would probably expect things like std::ios_base::badbit to be available whenever std::istream or std::ostream is, so we might as well say it explicitly.
I'm less sure with regards to <streambuf>, but it seems likely that most implementations already include it, since the template sources for most of the << and >> operators require it. Thus, requiring it doesn't seem to add any real overhead, and doing so may prevent an occasional surprise.
I see no particular need for including any of the other headers. <iosfwd> is subsumed in part by <istream> and <ostream>, and the other parts don't seem to be expected. <sstream> and <fstream> add functionality that typically isn't expected by people only using <iostream>, and they aren't included by most current implementations either. The case of <iomanip> is less clear cut—I don't think I've ever used <ostream> without it, if only for std::setw(), but since most current implementations don't include it either, I'd say that we should leave it out as well.
In [iostream.objects]:
Header <iostream> synopsis
#include <ios> #include <streambuf> #include <istream> #include <ostream> namespace std { extern istream cin; extern ostream cout; extern ostream cerr; extern ostream clog; extern wistream wcin; extern wostream wcout; extern wostream wcerr; extern wostream wclog; }The header <iostream> declares objects that associate objects with the standard C streams provided for by the functions declared in <cstdio>, and includes all of the headers neccesary to use these objects.
The standard now provides for extended integral types. It would be nice if the user were able to read and write them, as well as declaring variables of the type. And it is impossible to write such code oneself without knowing which typedef's correspond to standard types, and which ones correspond to extended types.
This depends somewhat on other proposals. If concepts make it in, and it is possible to restrict a template to instantiations which are integral types, then replacing << and >> with a single template, restricted to integral types, might be an option. Otherwise...
In [istream], in the class definition:
basic_istream<charT,traits>& operator>>(bool& n); basic_istream<charT,traits>& operator>>(short& n); [...] basic_istream<charT,traits>& operator>>(double& f); basic_istream<charT,traits>& operator>>(long double& f); // In addition, the implementation shall provide overloads of >> // for all extended integral types, with the same semantics // as those for the standard integral types
and in [ostream], in the class definition:
basic_ostream<charT,traits>& operator<<(bool n); basic_ostream<charT,traits>& operator<<(short n); [...] basic_ostream<charT,traits>& operator<<(double f); basic_ostream<charT,traits>& operator<<(long double f); // In addition, the implementation shall provide overloads of << // for all extended integral types, with the same semantics // as those for the standard integral types
Note that this really implies some work in [locale.num.get] and [locale.nm.put] as well; they should support at least the types intmax_t and uintmax_t, if these are larger than long long and unsigned long long.
Almost all systems are capable of doing more with files than is supported by std::filebuf; most are also capable of treating other types of objects like files, once they have been constructed. Typical examples include locking, synchronized access, or reading and writing pipes or sockets. These functionalities are not universal, and it is not proposed to add them in any way to the standard, but it does seem reasonable to make it possible for the user to access them, without foregoing the standard types for what they do support. The idea is thus to simple make the underlying type used to identify the open file known to the user, by means of a typedef, and to provide functions for reading this identifier from an open std::filebuf, and associating such an identifier to an pstd::filebuf.
Note that when a user associates such an identifier to a std::filebuf, the file is considered open, unless the identifier has a special predefined value meaning closed. When the user makes such an association, it is his responsibility to open and close the file; the function std::filebuf::close cannot be used, and it is undefined behavior to call it. (It is expected that implementations on platforms such as Unix or Windows, where the system level close works for such identifiers, even when they do not identify an actual file, the implementor will support std::filebuf::close for such file identifiers, as an extention. I'm not sure that this is the case of all systems, however, and I can easily imagine systems where a different system request would be used to close such an object.)
In [fstreams]
The header <fstream> defines four class templates and eight types that associate stream buffers with files and assist reading and writing files.
Header <fstream> synopsis
namespace std { typedef implementation defined native_file_descriptor; const native_file_descriptor invalid_file_descriptor; template <class charT, class traits = char_traits<charT> > class basic_filebuf; typedef basic_filebuf<char> filebuf; typedef basic_filebuf<wchar_t> wfilebuf; template <class charT, class traits = char_traits<charT> > class basic_ifstream; typedef basic_ifstream<char> ifstream; typedef basic_ifstream<wchar_t> wifstream; template <class charT, class traits = char_traits<charT> > class basic_ofstream; typedef basic_ofstream<char> ofstream; typedef basic_ofstream<wchar_t> wofstream; template <class charT, class traits = char_traits<charT> > class basic_fstream; typedef basic_fstream<char> fstream; typedef basic_fstream<wchar_t> wfstream; }
In [filebuf]
namespace std { template <class charT, class traits = char_traits<charT> > class basic_filebuf : public basic_streambuf<charT,traits> { public: [...] // 27.8.1.3 Members: bool is_open() const; native_file_descriptor file_descriptor() const; basic_filebuf<charT,traits>* open(const char* s , ios_base::openmode mode); basic_filebuf<charT,traits>* open(const string& s , ios_base::openmode mode); basic_filebuf<charT,traits>* open( native_file_descriptor n) ; basic_filebuf<charT,traits>* close(); protected: // 27.8.1.4 Overridden virtual functions: [...] }; }
In [filebuf.members], add or modify the following descriptions of functions:
native_file_descriptor file_descriptor() const;
Returns: the native file descriptor. This value is guaranteed to be the same as invalid_file_descriptor if !is_open(), otherwise, the value is implementation defined (but should allow the user to make calls to the OS API which affect the same underlying file in the system).
basic_filebuf<charT,traits>* open(native_file_descriptor n, ios_base::openmode mode);
Effects: If is_open() != false, returns a null pointer. Otherwise, initializes the filebuf as required. It then treats n as if it were the return value of the system level open which would have been performed for the mode parameter. If n corresponds to a legal file descripter in the native OS, the open operation is deemed to have succeeded, otherwise, it is deemed to have failed.
Returns: this if successful, a null pointer otherwise.
basic_filebuf<charT,traits>* close();Effects: If is_open() == false, returns a null pointer. If a put area exists, calls overflow(traits::eof()) to flush characters. If the last virtual member function called on *this (between underflow, overflow, seekoff, and seekpos) was overflow then calls a_codecvt.unshift (possibly several times) to determine a termination sequence, inserts those characters and calls overflow(traits::eof()) again. Finally if the file was opened by one of the open functions taking a string as argument, it closes the file (as if by calling std::fclose(file)). If any of the calls to overflow or std::fclose fails then close fails. Sets the native file descripter to an invalid value.
Returns: this on success, a null pointer otherwise.
Postcondition: is_open() == false.
This may be a bit premature, but if, as seems likely, threads are added to the standard, users will want to be able to lock the standard iostream objects, particularly std::cerr, in order to do things like:
std::cerr << "value = " << value << std::endl ;safely. In this case, of course, "users" may include library authors, which makes it almost impossible for the user to establish a global mutex object for the entire application. Given this, there is definite advantage in providing this in the standard.
(In the following, please consider std::mutex as as stand-in for any type of locking object that the committee finally adopts.)
My first consideration is that std::ios_base should not contain a std::mutex object. Most iostream objects (at least in my code) do not need it, so it is a cost which would frequently be paid when not needed. I also do not like the idea of the standard iostream objects being somehow special. And I rather prefer that the locking be explicit; just locking the individual << and >> operators isn't usually necessary, and when it is, it usually isn't sufficient, since the granularity is too low. With this in mind, I propose a global function which returns a mutex associated with the stream:
std::mutex& stream_lock( std::ios_base& ) ;(No guarantee that different streams have different mutexes, but a guarantee that two calls with the same stream return the same mutex.) The main goal here is to support things like:
std::mutex::scoped_lock l( stream_lock( std::cerr ) ) ; std::cerr << ...
Formally, just having a single mutex and always returning it would be conforming. I would encourage implementations, however, to use lazy creation of the mutexes, with some sort of map, and with the destructor of std::ios_base ensuring that the mutex was correctly destructed, if it had been created.