Document number: N2629=08-0139
Date: 2008-05-19
Project:
Programming Language C++, Library Working Group
Reply-to:
Beman Dawes <bdawes at acm.org>
Introduction
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).
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.
As an ABI breaking change, the proposal appears more suitable to C++0x than TR2.
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 implementaton. 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.
Daniel Krügler identified mistakes and inconsistencies in a draft of this paper, 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, after pubsync(), add:
const error_code& puberror() const;
To 27.5.2 Class template basic_streambuf<charT,traits> [streambuf], basic_streambuf synopsis, after sync(), add:
virtual const error_code& error() const;
To 27.5.2.2.2 Buffer management and positioning [streambuf.buffer], add:
const error_code& puberror() const;
Returns:
error()
.
To 27.5.2.4.2 Buffer management and positioning [streambuf.virt.buffer], add:
virtual const error_code& error() const;
Effects: Returns a reference to an error_code object representing the last failure reported by a derived class function in a way that is defined separately for each class derived from
basic_streambuf
in this clause ([stringbuf.virtuals], [filebuf.virtuals]).Default behavior: Returns
error_code(ioerrc::streambuf)
.
To 27.7.1 Class template basic_stringbuf [stringbuf] synopsis, overridden virtual functions, add:
const error_code& error() const;
To 27.7.1.4 Overridden virtual functions [stringbuf.virtuals], add:
const error_code& error() const;
Effects: Returns a reference to an
error_code
object representing the last failure reported by abasic_stringbuf
function. The set of possibleerror_code
values and categories ([syserr.errcode.overview]) is implementation-defined. [Note: implementations are encouraged to return the most specificerror_code
value and category available. -- end note]
To 27.8.1.1 Class template basic_filebuf [filebuf] synopsis, overridden virtual functions, add:
const error_code& error() const;
To 27.8.1.5 Overridden virtual functions [filebuf.virtuals], add:
const error_code& error() const;
Effects: Returns an
error_code
object representing the last failure reported by abasic_filebuf
function. The set of possibleerror_code
values and categories ([syserr.errcode.overview]) is implementation-defined. [Note: implementations are encouraged to return the most specificerror_code
value and category available. -- end note]
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