Document number:       N2655=08-0165
Date:                            2008-06-27
Project:                        Programming Language C++, Library Working Group
Reply-to:                      Beman Dawes <bdawes at acm.org>                                     

Detailed Reporting for Input/Output Library Errors (Revision 1)

Introduction
Revision History
Motivation
Approach
Impact on existing code
C++0x or TR2?
Implementation experience
Sample code
Revision history
Acknowledgements
Proposed wording

Introduction

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).

Revision History

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.

Motivation

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.

Approach

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.

Impact on existing code

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.

C++0x or TR2?

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.

Implementation experience

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.

Sample code

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

Revision history

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.

Acknowledgements

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.

Proposed wording

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 class error_category.

The object’s default_error_condition and equivalent virtual functions shall behave as specified for the class error_category. The object’s name 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 set failbit or badbit error flags shall ensure that subsequent calls to error() return a reference to an error_code object that identifies the error. The error_code object shall either have a category of io_category, or be an error_code object returned by rdbuf()->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, otherwise error_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 throw basic_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 by clear(rdstate() | state) (which may throw basic_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,
— the getloc() member to a copy the global locale, locale(), at the time of construction.
— the error() member to error_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 member imbue 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 the ec argument when throwing ios_base::failure exceptions.  [Note: The default value of ec 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 and strcmp(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 and strcmp(what(), msg ) == 0