Document number: N2655=08-0165
Date: 2008-06-27
Project:
Programming Language C++, Library Working Group
Reply-to:
Beman Dawes <bdawes at acm.org>
Introduction
Revision History
Motivation
Approach
Impact on existing code
C++0x or TR2?
Implementation experience
Sample code
Revision history
Acknowledgements
Proposed wording
This paper proposes adding detailed error reporting, based on C++ standard library System error support (19.4), to the C++ standard library's Input/output library (27).
This is a revision of
N2629. The only change is moving the most recent error storage to
basic_streambuf
from the derived class, eliminating the need for an
added virtual function and simplifying the proposal.
Lack of detailed error reporting has long been a concern about the C++ I/O Streams library. For example, The C++ Standard Library by Nicolai Josuttis, complained in 1999:
Unfortunately, the standard does not require that the exception object includes any information about the erroneous stream or the kind of error.
The problem is particularly acute for failures to open a file. Such failures are extremely common and there are numerous possible reasons for failure, such as the file not being found, the permissions being wrong, and so forth. Diagnosing these failures without knowing the exact reason for failure is difficult, irritating, and time wasting.
The standard library now supplies support for portable reporting of errors from the host operating system or other low-level API's that are typically used to implement file based I/O streams. See 19.4 System error support [syserr]. Errors may be reported by either an error code or by an exception containing an error code. Additional categories of errors, such as those arising from the I/O streams themselves are also supported.
Failures from the current iostreams library may be reported by either setting I/O state
bits or by throwing an exceptions. The exception thrown in such cases is
ios_base::failure
, which is publicly derived from the standard library
class exception
. As iostream failures are typically a runtime problem detected by the host
system, we would like this exception to derive from std::system_error
so that a detailed error code is available.
Failures detected by checking I/O state bits are augmented by keeping the
last error code as part of the I/O state and making it available via an
basic_ios::error
function returning a const std::error_code&
.
Additional setter and getter functions ensure that buffers and derived classes
can set/get the error code appropriately. Additional overloads to existing
functions ensure backward compatibility.
Much of the proposal is an add-on, and so will have no impact on existing code. In the few cases where signatures or semantics of existing functions change, default behavior is carefully chosen to eliminate breakage of existing code.
The one known case where existing code might break is user code that
depends on ios_base::failure
having specific derivation. Library
clause 17.4.4.7 has always granted implementors permission to derive classes
from classes other than the specified base, so such code is already suspect.
The proposal does break ABI compatibility in that a new virtual function is added and exception derivation changes.
At the June, 2008, meeting the Library Working Group decided that the proposal should be targeted toward TR2. There was insufficient time left for C++0x.
A C++03 proof-of-concept prototype of the proposal has been implemented by modifying the Apache C++ Standard Library. No difficulties were encountered and coding was straightforward. The effort took about a day and a half by someone who had no prior exposure to the implementation and was designing the interface during implementation. Virtually all changes were on paths through the code that are only executed when a failure has already been detected.
std::ifstream in; in.open("no-such-file"); if (!in) { std::cout << in.error().message() << '\n'; return 1; }
Running this as part of a program using the prototype implementation produced the output:
No such file or directory
The specification of std::system_error
as a base class of
std::basic_ios::failure
was originally proposed in
N2503, Indicating iostream failures with system_error
. Library
working group (LWG) discussion at the February, 2008, Bellevue meeting sparked
the current proposal.
Martin Sebor suggested moving the most recent error storage to
basic_streambuf
from the derived class. Daniel Krügler identified mistakes and inconsistencies in a draft of
the initial version of this proposal, and suggested many useful fixes. Alisdair Meredith initiated the
original
N2503 proposal.
The proposed wording assumes Library issues 805, posix_error::posix_errno concerns, and 832, Applying constexpr to System error support, have been accepted and applied to the working paper. If either of these has not been accepted, minor changes to the wording of this proposal will be required.
To 27.4 Iostreams base classes [iostreams.base], Header <ios>
synopsis, add:
enum class ioerrc { This name goes well with errc; review if LWG 805 fails or changes errc non_specific, stream, streambuf, rdbuf, exception, iomanip, filebuf, stringbuf, not_open, already_open, sentry, input_count, output_count }; concept_map ErrorCodeEnum<ioerrc> {}; Apply if N2620 is accepted template <> struct is_error_code_enum<ioerrc> : public true_type {} Apply if N2620 is not accepted constexpr error_code make_error_code(ioerrc e); constexpr error_condition make_error_condition(ioerrc e); extern const error_category* const io_category;
In a new sub-section, Error category object, a:
extern const error_category* const io_category;
io_category
shall point to a statically initialized object of a type derived from classerror_category
.The object’s
default_error_condition
andequivalent
virtual functions shall behave as specified for the classerror_category
. The object’sname
virtual function shall return a pointer to the string"IO"
.
At a location to be determined, add:
constexpr error_code make_error_code(ioerrc e);
Returns:
error_code(static_cast<int>(e), io_category)
.
constexpr error_condition make_error_condition(ioerrc e);
Returns:
error_condition(static_cast<int>(e), io_category)
.
At a location to be determined, add:
Functions in this clause specified to call the
setstate
function to setfailbit
orbadbit
error flags shall ensure that subsequent calls toerror()
return a reference to anerror_code
object that identifies the error. Theerror_code
object shall either have a category ofio_category
, or be anerror_code
object returned byrdbuf()->puberror()
.
To 27.4.4.1 basic_ios constructors [basic.ios.cons], basic_ios::init() effects table, add a row:
Element Value error()
error_code()
if sb is not a null pointer, otherwiseerror_code(ioerrc::streambuf)
.
To 27.4.4,Class template basic_ios [ios], basic_ios synopsis, after setstate(), add:
void setstate(iostate state, const error_code& ec);
To 27.4.4,Class template basic_ios [ios], basic_ios synopsis, after bad(), add:
const error_code& error() const; void error(const error_code& ec);
Change 27.4.4.3 basic_ios flags functions [iostate.flags], as indicated:
void setstate(iostate state);
Effects: Calls
clear(rdstate() | state)
setstate(state, error_code(ioerrc::nonspecific))
(which may throwbasic_ios::failure
(27.4.2.1.1)).[Note: This behavior ensures code written before the addition of the two argument overload continues to work. Use of the two argument overload may be preferred for new code. --end note]
void setstate(iostate state, const error_code& ec);
Effects: Calls
error(ec)
followed byclear(rdstate() | state)
(which may throwbasic_ios::failure
(27.4.2.1.1)).
const error_code& error() const;
Returns: The error code for the stream.
void error(const error_code& ec);
Posconditions:
error_code() == ec
To 27.5.2 Class template basic_streambuf<charT,traits> [streambuf], basic_streambuf synopsis, add public functions:
void error(error_code& ec);
const error_code& error() const;
Change 27.5.2.1 basic_streambuf constructors [streambuf.cons] as indicated:
basic_streambuf();
Effects: Constructs an object of class
basic_streambuf<charT,traits>
and initializes:
— all its pointer member objects to null pointers,
— thegetloc()
member to a copy the global locale,locale()
, at the time of construction.
— theerror()
member toerror_code(ioerrc::streambuf, ioerror_category)
.Remarks: Once the
getloc()
member is initialized, results of calling locale member functions, and of members of facets so obtained, can safely be cached until the next time the memberimbue
is called.
basic_streambuf(const basic_streambuf& rhs);
Effects: Constructs a copy of
rhs
.Postconditions:
— eback() == rhs.eback()
— gptr() == rhs.gptr()
— egptr() == rhs.egptr()
— pbase() == rhs.pbase()
— pptr() == rhs.pptr()
— epptr() == rhs.epptr()
— getloc() == rhs.getloc()
— error() == rhs.error()
Add a new section 27.5.2.2.6 Error reporting [streambuf.pub.error]:
void error(error_code& ec);
Effects: Stores a copy of
ec
.
const error_code& error() const;
Returns: A reference to the stored
error_code
object.
Change 27.4.2.1.1 Class ios_base::failure [ios::failure] as indicated:
namespace std { class ios_base::failure : public
exceptionsystem_error { public: explicit failure(const string& msg, const error_code& ec = ioerrc::non_specific); explicit failure(const char* msg, const error_code& ec = ioerrc::non_specific); virtual const char* what() const throw(); }; }The class
failure
defines the base class for the types of all objects thrown as exceptions, by functions in the iostreams library, to report errors detected during stream buffer operations.Implementations are encouraged to report
basic_ios::error()
as theec
argument when throwingios_base::failure
exceptions. [Note: The default value ofec
is provided to ensure code written before the addition of the second argument continues to work. It is the least desirable of possible values; more specific values ease error diagnosis. -- end note]
explicit failure(const string& msg, const error_code& ec = ioerrc::non_specific);
Effects: Constructs an object of class
failure
.Postcondition:
code() == ec
andstrcmp(what(), msg.c_str()) == 0
explicit failure(const char* msg, const error_code& ec = ioerrc::non_specific);
Effects: Constructs an object of class
failure
.Postcondition:
code() == ec
andstrcmp(what(), msg ) == 0