Document number:       N2629=08-0139
Date:                            2008-05-19
Project:                        Programming Language C++, Library Working Group
Reply-to:                      Beman Dawes <bdawes at acm.org>                                     

Detailed Reporting for Input/Output Library Errors

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

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?

As an ABI breaking change, the proposal appears more suitable to C++0x than TR2.

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

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.

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, 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 a basic_stringbuf function. The set of possible error_code values and categories ([syserr.errcode.overview]) is implementation-defined. [Note: implementations are encouraged to return the most specific error_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 a basic_filebuf function. The set of possible error_code values and categories ([syserr.errcode.overview]) is implementation-defined. [Note: implementations are encouraged to return the most specific error_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 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