Doc. no.   N2241=07-0101
Date:        2007-04-19
Project:     Programming Language C++
Reply to:   Beman Dawes <bdawes@acm.org>

Diagnostics Enhancements for C++0x (Rev. 1)

Introduction
Design Rationale
History
Acknowledgements
Proposed changes to the Working Paper
    Header <system_error>
    Class system_error
    Class error_category
    Class error_code

Introduction

Error conditions originating from the operating system or other low-level application program interfaces (API's) are typically reported via an integer representing an error code. On the other hand, the preferred mechanism for C++ libraries, including the standard library, is to report such errors by throwing an exception. Experience with the progenitors of the Filesystem Library (N1975), the Networking Library (N2054), and with similar Boost libraries has shown that such exceptions need to include the original error code to allow programs to undertake error-condition specific recovery actions.

That experience also found that while exceptions are the preferred default error code reporting mechanism, programs that use libraries dependent on low-level API's need overloads reporting error conditions via error code arguments and/or return values rather than via throwing exceptions. Otherwise, when errors are not exceptional occurrences and must be dealt with as they arise, programs become littered with try/catch blocks, unreadable, and very inefficient.

The proposed diagnostic enhancements were originally slated for TR2 (N2066). They are now proposed for C++0x so that they can be used by C++0x threads libraries.

Design Rationale

These diagnostic components are packaged in a single header to follow the standard library's practice of large granularity headers. The components are not simply added to <stdexcept> to hold down dependencies in <stdexcept>.

The header is named <system_error> to emphasize the role its contents play, and to emphasize the system_error exception class. This is also intended to present class error_code as an adjunct to error reporting by exception, rather than a completely separate error handling mechanism.

error_code  is designed as a value type so it can be copied without slicing and does not requiring heap allocation, but to have polymorphic behavior based on the error category. This is achieved by abstract base class error_category supplying the polymorphic behavior, and error_code containing a pointer to an object of a type derived from error_category.

The operator<< overload for error_code eliminates a misleading conversion to bool in code like cout << ec, where ec is of type error_code. It is also useful in its own right.

History

N1975, Filesystem Library Proposal for TR2, accepted for Library Technical Report 2 (TR2) at the Berlin meeting, included additional components to supplement the Standard Library's Diagnostics clause. Since then, these error reporting components have received wider public scrutiny and enhancements have been made to the design. The enhanced version has been used by N2054, Networking Library Proposal for TR2, demonstrating that these error reporting components are useful beyond the original Filesystem Library.

The original proposal viewed error categories as a binary choice between errno (i.e. POSIX-style) and the native operating system's error codes. The proposed components now allow as many additional error categories as are needed by either implementations or by users. The need to support additional error categories, for example, occurs in some networking library implementations because they are built on top of the POSIX getaddrinfo API that uses error codes not based on errno.

Acknowledgements

Christopher Kohlhoff and Peter Dimov made important contributions to the design. Comments and suggestions were also received from Pavel Vozenilek, Gennaro Prota, Dave Abrahams, Jeff Garland, Iain Hanson, Oliver Kowalke, and Oleg Abrosimov. Christopher Kohlhoff suggested several improvements to the N2066 paper. Johan Nilsson's comments led to several of the refinements in N2066

Proposed changes to the Working Paper

Add to Chapter 19, Diagnostics library [diagnostics], paragraph 2, an additional line in the Diagnostics library summary table, so it reads:

Table 26: Diagnostics library summary

Subclause Header(s)
19.1 Exception classes <stdexcept>
19.2 Assertions <cassert>
19.3 Error numbers <cerrno>
19.4 System error support <system_error>

Change 19.3, Error numbers [errno], as indicated:

Header <cerrno> (Table 28):

Table 28: Header <cerrno> synopsis

Type:         Name(s)
Macros: E2BIG EACCES EADDRINUSE EADDRNOTAVAIL EAFNOSUPPORT EAGAIN EALREADY EBADF EBADMSG EBUSY ECANCELED ECHILD ECONNABORTED ECONNREFUSED ECONNRESET EDEADLK EDESTADDRREQ EDOM EEXIST EFAULT EFBIG EHOSTUNREACH EIDRM EILSEQ EINPROGRESS EINTR EINVAL EIO EISCONN EISDIR ELOOP EMFILE EMLINK EMSGSIZE ENAMETOOLONG ENETDOWN ENETRESET ENETUNREACH ENFILE ENOBUFS ENODATA ENODEV ENOENT ENOEXEC ENOLCK ENOLINK ENOMEM ENOMSG ENOPROTOOPT ENOSPC ENOSR ENOSTR ENOSYS ENOTCONN ENOTDIR ENOTEMPTY ENOTRECOVERABLE ENOTSOCK ENOTSUP ENOTTY ENXIO EOPNOTSUPP EOTHER EOVERFLOW EOWNERDEAD EPERM EPIPE EPROTO EPROTONOSUPPORT EPROTOTYPE ERANGE EROFS ESPIPE ESRCH ETIME ETIMEDOUT ETXTBSY EWOULDBLOCK EXDEV errno

The contents are the same as the Standard C library POSIX header <errno.h>, except that errno shall be defined as a macro, and an additional macro EOTHER shall be defined to represent errors not specified by the POSIX standard. [Note: The intent is to remain in close alignment with the POSIX standard. --end note]

Add to Chapter 19, Diagnostics library [diagnostics], the following new subclause. The Project Editor may wish to identify it as 19.4 System error support [sys_err_support.code].

This subclause describes components that the standard library and C++ programs may use to report error conditions originating from the operating system or other low-level application program interfaces.

Component described in this subclause shall not change the value of errno ([errno]). Implementations are encouraged but not required to leave unchanged the error states provided by other libraries.

Header <system_error>

namespace std
{
  class system_error;
  class error_code;
  class error_category;

  extern const error_category& posix_category;
  extern const error_category& native_category;

  template <class charT, class traits>
    std::basic_ostream<charT,traits>&
      operator<<(basic_ostream<charT,traits>& os, const error_code & ec);

  size_t hash_value(const error_code & ec);
  enum posix_errno
  {
    address_family_not_supported,
    address_in_use,
    address_not_available,
    already_connected,
    argument_list_too_long,
    argument_out_of_domain,
    bad_address,
    bad_file_descriptor,
    bad_message,
    broken_pipe,
    connection_aborted,
    connection_already_in_progress,
    connection_refused,
    connection_reset,
    cross_device_link,
    destination_address_required,
    device_or_resource_busy,
    directory_not_empty,
    executable_format_error,
    file_exists,
    file_too_large,
    filename_too_long,
    function_not_supported,
    host_unreachable,
    identifier_removed,
    illegal_byte_sequence,
    inappropriate_io_control_operation,
    interrupted,
    invalid_argument,
    invalid_seek,
    io_error,
    is_a_directory,
    message_size,
    network_down,
    network_reset,
    network_unreachable,
    no_buffer_space,
    no_child_process,
    no_link,
    no_lock_available,
    no_message_available,
    no_message,
    no_protocol_option,
    no_space_on_device,
    no_stream_resources,
    no_such_device_or_address,
    no_such_device,
    no_such_file_or_directory,
    no_such_process,
    not_a_directory,
    not_a_socket,
    not_a_stream,
    not_connected,
    not_enough_memory,
    not_supported,
    operation_canceled,
    operation_in_progress,
    operation_not_permitted,
    operation_not_supported,
    operation_would_block,
    other,
    permission_denied,
    protocol_error,
    protocol_not_supported,
    read_only_file_system,
    resource_deadlock_would_occur,
    resource_unavailable_try_again,
    result_out_of_range,
    stream_timeout,
    text_file_busy,
    timed_out,
    too_many_files_open_in_system,
    too_many_files_open,
    too_many_links,
    too_many_synbolic_link_levels,
    value_too_large,
    wrong_protocol_type
  };

  bool operator==(const error_code& ec, posix_errno en);
  bool operator==(posix_errno en, const error_code& ec);
  bool operator!=(const error_code& ec, posix_errno en);
  bool operator!=(posix_errno en, const error_code& ec);
}

Error category objects

Predefined objects posix_category and native_category identify portable and native error codes, respectively.

Non-member functions

template <class charT, class traits>
  std::basic_ostream<charT,traits>&
    operator<<(basic_ostream<charT,traits>& os, const error_code & ec);

Effects: os << ec.category().name() << ':' << ec.value().

Returns: os.

size_t hash_value( const error_code& ec );

Returns:  A hash value representing ec.

enum posix_errno

The meaning and value of each posix_errno element shall correspond to the equivalent macro from header <cerrno>, as defined in the table below.

Name <cerrno> macro
address_family_not_supported EAFNOSUPPORT
address_in_use EADDRINUSE
address_not_available EADDRNOTAVAIL
already_connected EISCONN
argument_list_too_long E2BIG
argument_out_of_domain EDOM
bad_address EFAULT
bad_file_descriptor EBADF
bad_message EBADMSG
broken_pipe EPIPE
connection_aborted ECONNABORTED
connection_already_in_progress EALREADY
connection_refused ECONNREFUSED
connection_reset ECONNRESET
cross_device_link EXDEV
destination_address_required EDESTADDRREQ
device_or_resource_busy EBUSY
directory_not_empty ENOTEMPTY
executable_format_error ENOEXEC
file_exists EEXIST
file_too_large EFBIG
filename_too_long ENAMETOOLONG
function_not_supported ENOSYS
host_unreachable EHOSTUNREACH
identifier_removed EIDRM
illegal_byte_sequence EILSEQ
inappropriate_io_control_operation ENOTTY
interrupted EINTR
invalid_argument EINVAL
invalid_seek ESPIPE
io_error EIO
is_a_directory EISDIR
message_size EMSGSIZE
network_down ENETDOWN
network_reset ENETRESET
network_unreachable ENETUNREACH
no_buffer_space ENOBUFS
no_child_process ECHILD
no_link ENOLINK
no_lock_available ENOLCK
no_message_available ENODATA
no_message ENOMSG
no_protocol_option ENOPROTOOPT
no_space_on_device ENOSPC
no_stream_resources ENOSR
no_such_device_or_address ENXIO
no_such_device ENODEV
no_such_file_or_directory ENOENT
no_such_process ESRCH
not_a_directory ENOTDIR
not_a_socket ENOTSOCK
not_a_stream ENOSTR
not_connected ENOTCONN
not_enough_memory ENOMEM
not_supported ENOTSUP
operation_canceled ECANCELED
operation_in_progress EINPROGRESS
operation_not_permitted EPERM
operation_not_supported EOPNOTSUPP
operation_would_block EWOULDBLOCK
other EOTHER
owner_dead EOWNERDEAD
permission_denied EACCES
protocol_error EPROTO
protocol_not_supported EPROTONOSUPPORT
read_only_file_system EROFS
resource_deadlock_would_occur EDEADLK
resource_unavailable_try_again EAGAIN
result_out_of_range ERANGE
state_not_recoverable ENOTRECOVERABLE
stream_timeout ETIME
text_file_busy ETXTBSY
timed_out ETIMEDOUT
too_many_files_open_in_system ENFILE
too_many_files_open EMFILE
too_many_links EMLINK
too_many_synbolic_link_levels ELOOP
value_too_large EOVERFLOW
wrong_protocol_type EPROTOTYPE

[Note: other is used to indicate errors that have no POSIX equivalent. --end note]

Class system_error

The class system_error defines the type of objects thrown as exceptions to report error conditions that have an associated error code. Such error conditions typically originate from the operating system or other low-level application program interfaces.

namespace std
{
  class system_error : public std::runtime_error
  {
  public:
    system_error(error_code ec, const std::string& what_arg);
    system_error(error_code::value_type ev, const error_category& ecat,
                 const std::string& what_arg);

    const error_code& code() const throw();
    const char*       what() const throw();
  };
}
system_error(error_code ec, const std::string& what_arg);

Effects: Constructs an object of class system_error.

Postcondition: code() == ec
  && strcmp( this->runtime_error::what(), what_arg.c_str()) == 0

system_error(error_code::value_type ev, const error_category& ecat,
             const std::string& what_arg);

Effects: Constructs an object of class system_error.

Postcondition: code() == error_code(ev, ecat)
  && strcmp( this->runtime_error::what(), what_arg.c_str()) == 0

const error_code& code() const;

Returns: ec or error_code(ev, ecat), from the constructor, as appropriate.

const char* what() const;

Returns: A string incorporating this->runtime_error::what() and code.message().

[Note: One possible implementation would be:

if (msg.empty())
{
  try
  {
    msg = this->runtime_error::what();
    if ( code() )
    {
      if ( !msg.empty() ) msg += ": ";
      msg += code().message();
    }
  }
  catch (...) { return runtime_error::what(); }
}
return msg.c_str();

--end note]

Class error_category

The class error_category defines the base class for types used to identify the source and encoding of a particular category of error code.

[Note: Classes may be derived from error_category to support additional categories of errors. --end note]

namespace std
{
  class error_category
  {
  public:
    virtual const std::string & name() const=0;
    virtual posix_errno posix(error_code::value_type ev) const=0;
    virtual string message(error_code::value_type ev) const=0;
    virtual wstring wmessage(error_code::value_type ev) const=0;

    bool operator==(const error_category& rhs) const;
    bool operator!=(const error_category& rhs) const;
  };
}

Class error_category virtual members

Classes derived from error_category shall behave as specified in this subclause.

virtual const std::string & name() const=0;

Returns: a string naming the error category.

Throws: Nothing.

virtual posix_errno posix(error_code::value_type ev) const=0;

Returns: A posix_errno that corresponds to ev if such a corresponding POSIX error number exists, otherwise other.

[Note:  Since the value of ev is not bounded, the intent is that implementations translate commonly encountered values to the equivalent POSIX error number and translate the rest to other. --end note]

Throws: Nothing.

virtual string message(error_code::value_type ev) const=0;

Returns: A string that describes the error condition denoted by ev.

[Note: The intent is to return a locale sensitive string that describes the error corresponding to ev--end note.]

Throws: Nothing.

virtual wstring wmessage(error_code::value_type ev) const=0;

Returns: A string that describes the error condition denoted by ev.

[Note: The intent is to return a locale sensitive string that describes the error corresponding to ev--end note.]

Throws: Nothing.

Class error_category non-virtual members

bool operator==(const error_category& rhs) const;

Returns: this == &rhs.

bool operator!=(const error_category& rhs) const;

Returns: this != &rhs.

Class error_code

The class error_code defines the type of objects used to hold error code values, such as those originating from the operating system or other low-level application program interfaces.

[Note: Class error_code as an adjunct to error reporting by exception, rather than a completely separate error handling mechanism. --end note.]

namespace std
{
  class error_code
  {
  public:
    typedef int_least32_t value_type;

    // constructors:
    error_code();
    error_code(value_type val, const error_category& cat);
    error_code(posix_errno val);

    // modifiers:
    void assign(value_type val, const error_category& cat);
    error_code& operator=(posix_errno val);
    void clear();

    // observers:
    value_type             value() const;
    const error_category&  category() const;
    posix_errno            posix() const;
    string                 message() const;
    wstring                wmessage() const;
    operator unspecified-bool-type() const;

    // relationals:
    bool operator==(const error_code& rhs) const;
    bool operator!=(const error_code& rhs) const;

  private:
    value_type            _val; // for exposition only
    const error_category* _cat; // for exposition only
  };
}

Class error_code members

error_code();

Effects: Constructs an object of type error_code.

Postconditions: _val == 0 && _cat == &posix_category.

Throws: Nothing.

error_code(value_type val, const error_category& cat);

Effects: Constructs an object of type error_code.

Postconditions: _val == val && _cat == &cat.

Throws: Nothing.

error_code(posix_errno val);

Effects: Constructs an object of type error_code.

Postconditions: _val == static_cast<value_type>(val) && _cat == &posix_category.

Throws: Nothing.

void assign(value_type val, const error_category& cat);

Postconditions: _val == val && _cat == &cat.

Throws: Nothing.

error_code& operator=(posix_errno val);

Postconditions: _val == static_cast<value_type>(val) && _cat == &posix_category.

Throws: Nothing.

value_type value() const;

Returns: _val.

Throws: Nothing.

error_category category() const;

Returns: _cat.

Throws: Nothing.

posix_errno posix() const;

Returns:  category().posix(value()).

Throws: Nothing.

string message() const;

Returns:  category().message(value()).

Throws: Nothing.

wstring wmessage() const;

Returns:  category().wmessage(value()).

Throws: Nothing.

operator unspecified-bool-type() const;

Returns: if value() != value_type(), returns a value that will evaluate true in a boolean context; otherwise, returns a value that will evaluate false in a boolean context. The value type returned shall not be convertible to int.

Throws: nothing.

[Note: This conversion can be used in contexts where a bool is expected (e.g., an if condition); however, implicit conversions (e.g., to int) that can occur with bool are not allowed, eliminating some sources of user error. One possible implementation choice for this type is pointer-to-member. —end note ]

bool operator==(const error_code& rhs) const;

Returns: value() == rhs.value() && category() == rhs.category().

Throws: Nothing.

bool operator!=(const error_code& rhs) const;

Returns: !(*this == rhs).

Throws: Nothing.

non-member functions

bool operator==(const error_code& ec, posix_errno en);
bool operator==(posix_errno en, const error_code& ec);

Returns: ec.value() == static_cast<error_code::value_type>(en) && ec.category() == &posix_category

Throws: Nothing.

bool operator!=(const error_code& ec, posix_errno en);
bool operator!=(posix_errno en, const error_code& ec);

Returns: !(ec==en)

Throws: Nothing.