Doc. no.   N2415=07-0275
Date:        2007-09-09
Project:     Programming Language C++
Reply to:   Beman Dawes <bdawes@acm.org>
                Christopher Kohlhoff <chris@kohlhoff.com>               

Diagnostics Issues (Rev. 1)

Issue 1: Missing error_code::clear() specification
Issue 2: <system_error> should not force an implementation to expose <cerrno> macros
Issue 3: <functional> hash specialization for error_code
Issue 4: Class error_code constructor should be explicit
Issue 5: Class error_code constructors should be constexpr
Issue 6: Class error_category should be non-copyable
Issue 7: Problems with posix_errno overloads
Issue 8: [Withdrawn]
Issue 9: EOTHER hijacks POSIX macro space, constant name too general
Issue 10: Header dependencies are non-trivial
Issue 11: Overly complex error_code::value_type with circular dependencies
Issue 12: Missing throw specifications
Issue 13: Missing system_error constructor
Issue 14: error_category equality via address comparison
Issue 15: Extending error_code::value_types constants and user-defined error_category objects
Issue 16: POSIX versus native error_category
Issue 17: Broken error_category::message member functions
Issue 18: system_error::what and constructors
Issue 19: Should classes error_code and error_category be less-than comparable?
Issue 20: Which error_category applies to POSIX-like operating system errors unclear
Issue 21: Error enums add lots of names to namespace std
Issue 22: Out of memory should throw bad_alloc, not system_error
Issue 23: error_category::name() should return const char *
Issue 24: system_error what-less constructors needed
Acknowledgements

This paper is a major revision of N2296, Diagnostics Enhancements; Resolution of Small Issues. The name has been changed to reflect the wider scope of several proposals.

The resolution of many of the issues are interrelated or modify overlapping sections of text in the standard. The proposed resolutions of these issues are combined in the proposed resolution wording for issue 7.

The proposed resolutions for many of these issues were drafted by Beman Dawes. The issue submitters do not necessarily agree with these PR's.

Issue 1: Missing error_code::clear() specification

The clear() function is shown in the synopsis but is otherwise unspecified.

Reported separately by Pete Becker, PJ Plauger, and Benjamin Kosnik.

Proposed resolution

Specify the semantics. See proposed wording for issue 7.

Issue 2: <system_error> should not force an implementation to expose <cerrno> macros

In N2241, enum posix_errno did not specify values for each enumeration constant. Instead, a table of equivalence was provided showing the <cerrno> E* macro the constant was to be equivalent to. During editing, that table was folded into the enum for brevity. That was a useful change, but had the unintended side effect of making it look like <system_error> was required to expose the <cerrno> macros, which was not the intent.

Submitted by PJ Plaguer

Proposed resolution

Change the form of enum posix_errno in 19.4 System error support [syserr] as indicated:

enum posix_errno {
  address_family_not_supported = EAFNOSUPPORT, // EAFNOSUPPORT
  address_in_use = EADDRINUSE, // EADDRINUSE

 
... Apply same pattern of changes to all remaining constants for the enum.

At the end of 19.4 System error support [syserr] add:

The value of each enum posix_errno constant shall be the same as the value of the <cerrno> macro shown in the above synopsis.

Whether or not the <system_error> implementation actually exposes the <cerrno> macros is unspecified.

Issue 3: <functional> hash specialization for error_code

The member function hash_value should be replaced or supplemented by an explicit specialization of template class hash (in <functional>) so people can easily make an unordered container keyed on errors.

Submitted by PJ Plauger.

Proposed resolution

In 19.4.2.1 Class error_code overview [syserr.errcode.overview], removed:

size_t hash_value(const error_code& ec);

In 19.4.2.6 Class error_code non-member functions [syserr.errcode.nonmembers], remove:

size_t hash_value(const error_code& ec);

Returns: A hash value representing ec.

In 20.5 Function objects [function.objects], paragraph 2, Header <functional> synopsis, add:

template <> struct hash<std::error_code>;

Change 20.5.15 Class template hash [unord.hash] as indicated:

The unordered associative containers defined in clause 23.4 use specializations of hash as the default hash function. This class template is only required to be instantiable for integer types (3.9.1), floating point types (3.9.1), pointer types (8.3.1), and std::string, std::u16string, std::u32string, and std::wstring, and std::error_code.

Issue 4: Class error_code constructor should be explicit

Submitted by Alisdair Meredith.

Proposed resolution

No action necessary.

Rationale

The conversion constructor is important design element; without it error enum ease-of-use suffers badly.

Issue 5: Class error_code constructors should be constexpr

Objects of class error_code will commonly be constructed in very low-level systems code where efficiency is a critical consideration. Thus static initialization is quite desirable, and would remove the runtime and code size cost implicit in the change to enumeration style error values.

A possible fix is to apply constexpr.

Submitted by Chris Kohlhoff and Beman Dawes.

Proposed resolution

Take no action at this time.

Rationale

It doesn't appear at this time that constexpr works for the error_code constructors, because error_category isn't a literal type. Once an implementation of constexpr become available, it may be possible to develop a workaround, but until then no action should be taken.

Issue 6: Class error_category should be non-copyable

Class error_category equality is stated in terms of address comparison, taking advantage of the C++ language rule that no two objects can have the same address, and thus avoiding the possibility that independently created user-defined error categories could inadvertently compare equal.

Thus it makes no sense to allow error_category objects to be copyable.

Submitted by Pete Becker.

Proposed resolution

Make error_category non-copyable. See proposed wording for issue 7.

Issue 7: Problems with posix_errno overloads

The original Diagnostics proposal, N2174, provided error_code constants for POSIX errors. The LWG requested that these be changed to an enum, and enum posix_errno was accordingly provided  in the final proposal voted into the working paper. Since the enums are replacing objects of type error_code, rather than replacing simple integer constants, such a change has turned out to have considerable fallout.

The wording in the WP included several overloads on posix_errno introduced to make the enums workable. However, the introduced overloads have several problems:

A solution to these problems must ensure:

Proposed resolution

Change 19.4 System error support [syserr] as indicated. Note that is_error_code_enum and is_error_condition_enum will be eliminated when the library is conceptized.

namespace std {

class system_error;
class error_code;
class error_condition;
class error_category;

template< class T >
struct is_error_code_enum {
  static const bool value = false;
};

template< class T >
struct is_error_condition_enum {
  static const bool value = false;
};

namespace posix {
  enum posix_errno {
    address_family_not_supported = EAFNOSUPPORT,
    address_in_use = EADDRINUSE,
    ...
    value_too_large = EOVERFLOW,
    wrong_protocol_type = EPROTOTYPE
    };
}
template<> struct is_error_condition_enum<posix::posix_errno> {
  static const bool value = true;
};

bool operator==(const error_code& lhs, const error_code& rhs);
bool operator==(const error_code& ec code, posix_errno en const error_condition& condition);
bool operator==(posix_errno en const error_condition& condition, const error_code& ec code);
bool operator==(const error_condition& lhs, const error_condition& rhs);
bool operator!=(const error_code& lhs, const error_code& rhs);
bool operator!=(const error_code& ec code, posix_errno en const error_condition& condition);
bool operator!=(posix_errno en const error_condition& condition, const error_code& ec code);
bool operator!=(const error_condition& lhs, const error_condition& rhs);
bool operator<(const error_code& lhs, const error_code& rhs);
bool operator<(const error_condition& lhs, const error_condition& rhs);

error_code make_error_code(posix::posix_errno e);
error_condition make_error_condition(posix::posix_errno e);

} // namespace std

Users may specialize is_error_code_enum and is_error_condition_enum templates to indicate that a type is eligible for class error_code and error_condition automatic conversions respectively.

Change 19.4.1.1 Class error_category overview [syserr.errcat.overview] as indicated:

namespace std {

  class error_category {
  public:
    virtual ~error_category();
    error_category(const error_category&) = delete;
    error_category& operator=(const error_category&) = delete;
    virtual const string& name() const = 0;
    virtual posix_errno posix(error_code::value_type ev) const = 0;
    virtual error_condition default_error_condition(int ev) const;
    virtual bool equivalent(int code, const error_condition & condition) const;
    virtual bool equivalent(const error_code & code, int condition) const;
    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;
    bool operator<( const error_category & rhs ) const;
  };

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

} // namespace std

Change 19.4.1.2 Class error_category virtual members [syserr.errcat.virtuals] as indicated:

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

Returns: A value of type posix_errno that corresponds to ev if such a corresponding POSIX error number exists, otherwise other error_condition(ev, *this) [ Note: Since the possible values of ev are 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 bool equivalent(int code, const error_condition & condition) const;

Returns: default_error_condition( code ) == condition.

Throws: Nothing.

virtual bool equivalent(const error_code & code, int condition) const;

Returns: *this == code.category() && code.value() == condition.

Throws: Nothing.

Add to 19.4.1.3 Class error_category non-virtual members [syserr.errcat.nonvirtuals]:

bool operator<(const error_category & rhs) const;

Returns: less<const error_category*>()( this, &rhs ).

[Note: less([comparisons]) provides a total ordering for pointers. --end note]

Throws: Nothing.

Add a new section before 19.4.1.4 [syserr.errcat.objects], titled "Program defined classes derived from error_category":

To be supplied: Requirements for classes derived from error_category.

Add to 19.4.1.4 Error category objects [syserr.errcat.objects]:

To be supplied: Description of posix_category and system_category derived classes.

Change 19.4.2.1 Class error_code overview [syserr.errcode.overview] as indicated:

namespace std {

  class error_code {
  public:
    typedef int_least32_t value_type;

    // constructors:
    error_code();
    error_code(value_type int val, const error_category& cat);
    error_code(posix_errno val);
    template <class ErrorCodeEnum>
      error_code(ErrorCodeEnum e,
        typename enable_if<is_error_code_enum<ErrorCodeEnum> >::type* = 0);

    // modifiers:
    void assign(value_type int val, const error_category& cat);
    error_code& operator=(posix_errno val);
    template<typename ErrorCodeEnum>
      typename enable_if<is_error_code_enum<ErrorCodeEnum>, error_code>::type &
        operator=( ErrorCodeEnum val );;
    void clear();

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

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

    private:
    value_type int val_; // exposition only
    const error_category& cat_; // exposition only
  };

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

  size_t hash_value(const error_code& ec);

} // namespace std

Change 19.4.2.2 Class error_code constructors [syserr.errcode.constructors] as indicated:

error_code();

Effects: Constructs an object of type error_code.

Postconditions:  val_ == 0 and cat_ == posix_category &system_category.

Throws: Nothing.

error_code(value_type int val, const error_category& cat);

Effects: Constructs an object of type error_code.

Postconditions:  val_ == 0 and cat_ == cat.

Throws: Nothing.

error_code(posix_errno val);
template <class ErrorCodeEnum>
  error_code(ErrorCodeEnum e,
    typename enable_if<is_error_code_enum<ErrorCodeEnum> >::type* = 0);

Effects: Constructs an object of type error_code.

Postconditions: val_ == static_cast<value_type>(val) and cat_ == posix_category *this == make_error_code(val).

Throws: Nothing.

Change 19.4.2.3 Class error_code modifiers [syserr.errcode.modifiers] as indicated:

error_code(value_type int val, const error_category& cat);

Postconditions:  val_ == val and cat_ == cat.

Throws: Nothing.

error_code& operator=(posix_errno val);
template<typename ErrorCodeEnum>
  typename enable_if<is_error_code_enum<ErrorCodeEnum>, error_code>::type &
    operator=( ErrorCodeEnum val );

Postconditions: val_ == static_cast<value_type>(val) and cat_ == posix_category *this == make_error_code(val).

Returns: *this.

Throws: Nothing.

Change 19.4.2.4 Class error_code observers [syserr.errcode.observers] as indicated:

value_type int value() const;

Returns: val_.

Throws: Nothing.

...

posix_errno posix() const;
error_condition default_error_condition() const;

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

Throws: Nothing.

Change 19.4.2.5 Class error_code relational operators [syserr.errcode.relational] as indicated:

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

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

Throws: Nothing.

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

Returns: ec.value() == static_cast<error_code::value_type>(en) && ec.category() == posix_-
category
code.category().equivalent(code.value(), condition)
|| condition.category().equivalent(code, condition.value())
.

Throws: Nothing.

bool operator==(const error_condition& lhs, const error_condition& rhs);

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

Throws: Nothing.

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

Returns: !(lhs == rhs).

Throws: Nothing.

bool operator!=(const error_code& ec code, posix_errno en const error_condition& condition);
bool operator!=(posix_errno en const error_condition& condition, const error_code& ec code);
Returns: !(ec code == en condition).

Throws: Nothing.

bool operator!=(const error_condition& lhs, const error_condition& rhs);

Returns: !(lhs == rhs).

Throws: Nothing.

bool operator<(const error_code& lhs, const error_code& rhs);

Returns: lhs.category() < rhs.category()
  || (lhs.category() == rhs.category() && lhs.value() < rhs.value()).

Throws: Nothing.

bool operator<(const error_condition& lhs, const error_condition& rhs);

Returns: lhs.category() < rhs.category()
  || (lhs.category() == rhs.category() && lhs.value() < rhs.value()).

Throws: Nothing.

error_code make_error_code(posix::posix_errno e);

Returns: error_code(e, posix_category).

error_condition make_error_condition(posix::posix_errno e);

Returns: error_condition(e, posix_category).

Insert before 19.4.3 Class system_error [syserr.syserr], a new section:

19.4.n Class error_condition [syserr.errcondition]

19.4.n.1 Class error_condition overview [syserr.errcondition.overview]

The class error_condition describes an object used to hold values identifying error conditions. [ Note: error_condition values are portable abstractions, while error_code values ([syserr.errcode]) are implementation specific. —end note ]

namespace std {

  class error_condition {
  public:

    // constructors:
    error_condition();
    error_condition(int val, const error_category& cat);
    template <class ErrorConditionEnum>
      error_condition(ErrorConditionEnum e,
        typename enable_if<is_error_condition_enum<ErrorConditionEnum> >::type* = 0);

    // modifiers:
    void assign(int val, const error_category& cat);
    template<typename ErrorConditionEnum>
      typename enable_if<is_error_condition_enum<ErrorConditionEnum>, error_code>::type &
        operator=( ErrorConditionEnum val );
    void clear();

    // observers:
    int value() const;
    const error_category& category() const;
    string message() const;
    operator unspecified-bool-type () const;

  private:
    int val_; // exposition only
    const error_category& cat_; // exposition only
  };

} // namespace std

19.4.n.2 Class error_condition constructors [syserr.errcondition.constructors]

error_condition(); 

Effects: Constructs an object of type error_condition.

Postconditions: val_ == 0 and cat_ == posix_category.

Throws: Nothing.

error_condition(value_type val, const error_category& cat);

Effects: Constructs an object of type error_condition.

Postconditions: val_ == val and cat_ == cat.

Throws: Nothing.

template <class ErrorConditionEnum>
  error_condition(ErrorConditionEnum e,
    typename enable_if<is_error_condition_enum<ErrorConditionEnum> >::type* = 0);;

Effects: Constructs an object of type error_condition.

Postconditions: *this == make_error_condition(val).

Throws: Nothing.

19.4.n.3 Class error_condition modifiers [syserr.errcondition.modifiers]

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

Postconditions: val_ == val and cat_ == cat.

Throws: Nothing.

template<typename ErrorConditionEnum>
  typename enable_if<is_error_condition_enum<ErrorConditionEnum>, error_code>::type &
    operator=( ErrorConditionEnum val );;

Postconditions: *this == make_error_condition(val).

Throws: Nothing.

void clear();

postcondition: value() == 0 && category() == posix_category

19.4.n.4 Class error_condition observers [syserr.errcondition.observers]

value_type value() const;

Returns: val_.

Throws: Nothing

const error_category& category() const;

Returns: cat_.

Throws: Nothing.

string message() const;

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

Throws: Nothing.

operator unspecified-bool-type () const;

Returns: If value() != 0, returns a value that will evaluate true in a boolean context; otherwise, returns a value that will evaluate false. The return type 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 ]

Rationale

Unintended consequences

For example, say the user writes:

error_code ec;
some_operation(ec);
if (ec == boo_boo)
  { ... }

operator == is currently overload on enum posix_errno , so if boo_boo is a posix_errno constant, the result will be as if the code read:

if (ec.posix() == boo_boo)

But this is assuming the users is trying to write portable code. If the user instead is writing system specific code, what is really wanted is the equivalent of:

if (ec == error_code(boo_boo, boo_boo's category))

With the interface as currently specified, the user would have to write that out in full. Unfortunately, the user might well opt for this instead:

if (ec.value() == boo_boo)

Unfortunately, that is really error prone. If the implementation happens to return an error_code with the same value as boo_boo, but in a different category, so the result will be true when it should be false.

Issue 8: [Withdrawn]

Issue 9: EOTHER hijacks POSIX macro space, constant name too general.

The working draft specifies other = EOTHER in the specification of posix_errno in 19.4 System error support, with the specification that EOTHER is not a POSIX-defined macro in 19.3 Error numbers. Earlier papers stated that this new macro will be added to the include file cerrno.

The problem with this is that C++ is hijacking the POSIX macro space, which should be avoided as future versions of the POSIX standard may add more error macros, including EOTHER, but may have it mean something else.

Submitted by Benjamin Kosnik.

Proposed resolution

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

The header <cerrno> is described in (Table 29). Its contents are the same as the 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 ]

From 19.3 Error numbers [errno], Header <cerrno> synopsis, strike EOTHER.

Change 19.4 System error support [syserr], Header <system_error> synopsis as indicated:

other = EOTHER,

For 19.4.1.2 Class error_category virtual members [syserr.errcat.virtuals] paragraph 3, see issue 7 proposed wording.

Issue 10: Header dependencies are non-trivial

In section 19.4.2.6 Class error_code non-member functions, an ostream inserter for error_code is defined for the primary ostream template. This will necessitate the inclusion of the ostream include file, which has large dependencies on other standard library features. The practical result is that the system_error include, far from being a lightweight, stand-alone error-reporting facility, will instead come with significant compile time cost.

Fix: Move the error_code inserter to the ostream include file, or relax the requirements such that only char and wchar_t specializations are required.

Submitted by Benjamin Kosnik.

Proposed resolution

Move the following text from 19.4.2.1 Class error_code overview [syserr.errcode.overview] to the non-member section of the synopsis in 27.6.2.1 Class template basic_ostream [ostream], making the indicated changes:

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

template <class charT, class traits>
  basic_ostream<charT,traits>& operator<<(basic_ostream<charT,traits>&&, const error_code&);

Move the following text from 9.4.2.6 Class error_code non-member functions [syserr.errcode.nonmembers] to a new sub-section following 27.6.2.6.4 Character inserter function templates [ostream.inserters.character], making the indicated changes:

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

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

Effects: os out << ec.category().name() << ’:’ << ec.value().

Returns: out

Issue 11: Overly complex error_code::value_type with circular dependencies

Section 19.4.2.1 specifies error_code::value_type as typedef int_least32_t value_type;. However, this type is used in error_category, which has to be defined before any of the trivial members in error_code can be made inline.

In addition, this complexity results in unsightly casting in the error_code relational operators and constructor specifications.

Possible Fix: Assume error_code::value_type is of type int, as specified by both C and POSIX.

Submitted by Benjamin Kosnik.

Proposed resolution

Change the type to int. See proposed wording for issue 7.

Rationale

The typedef is a historical artifact left over from early development of the class. An int is sufficient for all operating systems we are aware of.

Issue 12: Missing throw specifications

In 19.4.1.2 Class error_category virtual members, all member functions are defined to not throw, but do not have throw() specifications.

The same hold for error_code member functions that are modifiers and observers. i.e, sections 19.4.2.3 and 19.4.2.4.

Proposed Fix: Add in missing throw() specifications.

Submitted by Benjamin Kosnik.

Proposed resolution

No action necessary.

Rationale

The Working Paper is inconsistent in its use of  throw() specifications. Many LWG members prefer the "Throws: Nothing" specification as used in the current working paper wording for diagnostics.

Issue 13: Missing system_error constructor

All existing classes derived from std::exception provide either a default constructor or a constructor taking a single const std::string& parameter. The current specification for system_error breaks with this.

Submitted by Benjamin Kosnik.

Proposed Fix: Add a constructor to system_error that takes a const std::string& argument.

Proposed resolution

No action necessary.

Rationale

The design of the constructors, including order of arguments, was a considered decision after much discussion with Peter Dimov and others on the Boost list. A primary objective of system_error is to report an error_code. We don't want to encourage construction of system_error objects that don't have one. Thus the constructors all take error_code or equivalent arguments first in addition to the what string argument. Not supplying a default constructor avoids the nonsensical construction of system_error representing "no error".

Issue 14: error_category equality via address comparison

Section 19.4.1.3 Class error_category non-virtual members specifies address comparisons between error_category base objects to determine equality and inequality. This can be fragile or difficult in shared memory or distributed systems, and may lead to incorrect results for equivalent error_category objects.

Submitted by Benjamin Kosnik.

Fix: Equality compare on internal details of the message catalog characteristics.

Proposed resolution

No action necessary.

Rationale

Resolved by Issue 6: Class error_category should be non-copyable. No further action needed.

Issue 15: Extending error_code::value_types constants and user-defined error_category objects

Section 19.4 specifies posix_errno as an enum. As such, there is no way to extend it for native error values, which will presumably be in the form of another enum, in some user-defined space.

Section 19.4.1.4 Error category objects vaguely specifies posix_category and native_category as external objects. As such, the implementation details are likely to remain private, making simple derivation and extension tricky or difficult.

Submitted by Benjamin Kosnik.

Proposed resolution

To 19.4.1.1 Class error_category overview [syserr.errcat.overview], at the end, add:

[Example: A user or third-party library can provide an additional error_category:

extern const error_category& my_error_category;

enum my_error { boo_boo_1=1, boo_boo_2 };

namespace std {
  inline error_code make_error_code(my_error e)
    { return error_code( e, my_error_category ); }
}

-- end example]

After 17.4.4.8 Restrictions on exception handling [res.on.exception.handling], add a new sub-section (presumably 17.4.4.9):

17.4.4.9 Value of error codes

Certain functions in the C++ Standard Library report errors via a std::error_code([syserr.errcode.overview]) object. That object's category() member shall return a reference to std::system_category for errors originating from the operating system, or a reference to an implementation-defined error_category object for errors originating elsewhere. The implementation shall define the possible values of value() for each of these error categories. [Example: For a POSIX-based operating system, an implementation is encouraged to define the std::system_category values as identical to the POSIX errno values, plus additional values as defined by the operating system's documentation. An implementation on a non-POSIX operating system is encouraged to define values identical to the operating system's error values. For errors that don't originate from the operating system, the implementation may provide enums for the associated values. -- end example]

Rationale

posix_errno should not be extended by users or implementers. Only the POSIX committee can extend the set of POSIX errnos. Thus the concern about 19.4 is not a defect.

Regarding 19.4.1.4; yes, the implementation details of error_categories are private, and that is deliberate encapsulation.

The concern over derivation and extension can be addressed in several ways. The proposed resolution's example should clarify what users need to do. For implementers, the proposed addition to clause 17 provides general guidance. An example of actual implementation could be outside the scope of the standard. For illustration, extracts from the current Boost implementation for several platforms are given below:

    //  Operating system specific interfaces  --------------------------------//

    //  The interface is divided into general and system-specific portions to
    //  meet these requirements:
    //
    //  * Code calling an operating system API can create an error_code with
    //    a single category (system_category), even for POSIX-like operating
    //    systems that return some POSIX errno values and some native errno
    //    values. This code should not have to pay the cost of distinguishing
    //    between categories, since it is not yet known if that is needed.
    //
    //  * Users wishing to write system-specific code should be given enums for
    //    at least the common error cases.
    //
    //  * System specific code should fail at compile time if moved to another
    //    operating system.

#ifdef BOOST_POSIX_API

    //  POSIX-based systems  -------------------------------------------------//

    //  To construct an error_code after a API error:
    //
    //      error_code( errno, system_category )

    //  User code should use the portable "posix" enums for POSIX errors; this
    //  allows such code to be portable to non-POSIX systems. For the non-POSIX
    //  errno values that POSIX-based systems typically provide in addition to 
    //  POSIX values, use the system specific enums below.

# ifdef __CYGWIN__

    namespace cygwin
    {
      enum cygwin_errno
      {
        no_net = ENONET,
        no_package = ENOPKG,
        no_share = ENOSHARE,
      };
    }  // namespace cygwin

    template<> struct is_error_code_enum
      { static const bool value = true; };

    inline error_code make_error_code(cygwin::cygwin_errno e)
      { return error_code( e, system_category ); }

# elif defined(linux) || defined(__linux) || defined(__linux__)

    namespace Linux  // linux lowercase name preempted by use as predefined macro
    {
      enum linux_error
      {
        advertise_error = EADV,
        bad_exchange = EBADE,
        bad_file_number = EBADFD,
        bad_font_format = EBFONT,
        bad_request_code = EBADRQC,
        ...
        unclean = EUCLEAN,
      };
    }  // namespace Linux

    template<> struct is_error_code_enum
      { static const bool value = true; };

    inline error_code make_error_code(Linux::linux_error e)
      { return error_code( e, system_category ); }

# endif


#elif defined(BOOST_WINDOWS_API)

    //  Microsoft Windows  ---------------------------------------------------//

    //  To construct an error_code after a API error:
    //
    //      error_code( ::GetLastError(), system_category )

    namespace windows
    {
      enum windows_error
      {
        success = 0,
        // These names and values are based on Windows winerror.h
        invalid_function = ERROR_INVALID_FUNCTION,
        file_not_found = ERROR_FILE_NOT_FOUND,
        path_not_found = ERROR_PATH_NOT_FOUND,
        too_many_open_files = ERROR_TOO_MANY_OPEN_FILES,
        access_denied = ERROR_ACCESS_DENIED,
        invalid_handle = ERROR_INVALID_HANDLE,
        arena_trashed = ERROR_ARENA_TRASHED,
        not_enough_memory = ERROR_NOT_ENOUGH_MEMORY,
        ...
        already_exists = ERROR_ALREADY_EXISTS,
      };
    }  // namespace windows

    template<> struct is_error_code_enum
      { static const bool value = true; };

    inline error_code make_error_code(windows::windows_error e)
      { return error_code( e, system_category ); }

#else
#  error BOOST_POSIX_API or BOOST_WINDOWS_API must be defined
#endif

Issue 16: POSIX versus native error_category

Should posix_category and native_category be able to have the same address?

In theory, yes. In practice, real POSIX-based operating systems such as Linux add additional error codes, so the error categories have to be different. That allows an implementation to use error_code(errno, native_category) to construct an error_code. If the POSIX values of errno were a different category from the non-POSIX values, an expensive lookup would have to be done to assign the category.

Should the member function error_category::posix exist for the predefined object posix_category? Isn't this a no-op, and best added to the class implementing native_category?

The error_category virtuals exist to support the equivalent error_code and error_condition member functions, allowing user-defined error categories. This mechanism unravels if error_category::posix (or its issue 7 replacements) isn't present for all error categories.

Should the division be between underlying system errors needed by the C++0x standard library and user-defined error messages, instead of between POSIX and native?

Early versions of the current design did divide the world along standard library / native lines. The design evolved into a POSIX / operating-system / user-defined breakdown because it met specific needs of users and third-party library suppliers, who must deal with a variety of real-world use cases, and implementers who sometimes must rely on API's other than those of the operating system.. That isn't to say another design wouldn't work, but this design is known to work and represents successful existing practice. Note that the dichotomy between POSIX and native is clarified greatly by the introduction of class error_condition in issue 7.

Submitted by Benjamin Kosnik.

Proposed resolution

Change the name native_category to system_category. See proposed wording for issue 7.

Rationale

The name native_category seems to be causing some confusion. It is changed to system_category to more clearly indicate this category represents errors from the operating system itself rather than some other library used by the implementation.

The answers to the questions are given in italics above.

Issue 17: Broken error_category::message member functions

Section 19.4.1.2 Class error_category virtual members defines members message and wmessage, and notes an intention to provide a locale-specific string. However, there is no indication that this is integrated into the existing standard library infrastructure for locale, ie std::locale.

There are two distinct issues: one, construction of error_category would have to take some kind of std::locale parameter, some kind of getter/setter would have to exist to provide locale data, or message would have to take a std::locale argument. This last option would require serious re-work of the system_error::what member function.

The second issue is code conversion so that wmessage could return a wide string from a message catalog. The use case for this is theoretical at the moment, as wide-character message catalogs are not known. For this, std::codecvt would be the preferred mechanism, and that depends on three template parameters. If this route is followed, it may make sense to templatize the entire class, and separate out message for char instantiations and wmessage for wchar_t instantiations.

Note that error_code has these same member functions, which forward to the error_category member functions, so this could be considered a defect in both class specifications.

Submitted by Benjamin Kosnik.

Proposed resolution

Change 19.4.1.1 Class error_category overview [syserr.errcat.overview] paragraph 1 as indicated:

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

Change 19.4.1.2 Class error_category virtual members [syserr.errcat.virtuals] paragraph 4 and 5 as indicated:

virtual string message(error_code::value_type ev) const = 0;
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-specific string that describes the error corresponding to ev. --end note ]

Throws: Nothing.

Change 19.4.2.1 Class error_code overview [syserr.errcode.overview] paragraph 1 as indicated:

string message() const;
wstring wmessage() const;

Change 19.4.2.4 Class error_code observers [syserr.errcode.observers] beginning at paragraph 6 as indicated:

string message() const;

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

Throws: Nothing.

wstring wmessage() const;

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

Throws: Nothing.

Change 19.4.3.2 Class system_error members [syserr.syserr.members] as indicated:

const char *what() const;

Returns: An NTBS incorporating runtime_error::what() and code().message().

[ Note: One possible implementation would be:

if (msg.empty())
{
  try
  {
    std::string tmp = runtime_error::what();
    if (code())
    {
    if (!tmp.empty())
      tmp += ": ";
    tmp += code().message();
    }
  swap(msg, tmp);
  }
catch(...)
{
  return runtime_error::what();
}
return msg.c_str();

end note ]

Rationale

Discussed by the LWG in Toronto. None of the current standard exception classes worry about wide messages or locales, so a fix for one particular exception probably isn't the right approach. Better to remove the functions until the problem and solution can be more fully explored.

After the Toronto meeting, an email discussion between Beman Dawes, Peter Dimov, Christopher Kohlhoff, and Benjamin Kosnik discussed possible solutions without reaching consensus. The most likely solution was something along the lines of:

string error_message(error_code ec, const locale& loc )
{
  messages<char>& f = use_facet< messages<char> >( loc );
  int cat = f.open( ec.category().name(), loc );
  string r = f.get( cat, 0, ec.value(), "" );
  f.close( cat );
  return r;
}

While this approach looked promising, it wasn't clear how it would work with user-defined error categories, and whether or not it was actually practical for the full range of operating systems.

Issue 18: system_error::what and constructors

In 19.4.3.2 Class system_error members, the constructor definitions and what specification collude to force exception-unsafe behavior, and multiple std::string data members.

This in effect mandates elaborate string modification as part of system_error::what, which may cause unanticipated exceptions to occur, in addition to being inefficient.

These specifications are a break with the existing stdexcept requirements and the exception hierarchy design philosophy: by making what virtual, users can reasonably expect to have derived classes with different return strings from what. Indeed, that's the point.

In addition, with all these additional requirements, there still exists no way to pass in locale-specific strings.

See issue 17.

Submitted by Benjamin Kosnik.

Proposed resolution

Change 19.4.3.2 Class system_error members [syserr.syserr.members], paragraph 4, as indicated:

const error_code& code() const throw();

Change 19.4.3.2 Class system_error members [syserr.syserr.members], paragraph 5, as indicated:

const char *what() const throw();

No additional action necessary.

Rationale

Exception safety is dealt with by the proposed resolution. The data members are not specified; implementations are free to use a std::string or some completely different mechanism. Implementations will presumably use lazy evaluation, so much of the cost will only occur if what() is actually called. No action necessary.

system_error::what  and code do specify throw() in the synopsis to deal with the exception issue, but not in the member descriptions. See proposed resolution.

The semantics of what() are extremely valuable to users, and any inefficiency is minor compared to the general overhead of an exception being thrown. Existing code, such as main() exception monitors that catch std::exception and report the results of exception::what(), works without modification for std::system_error. and that's very desirable.  No additional action necessary.

what() is virtual in the base class, so is virtual in class system_error too. And system_error::what does normally return a different string than runtime_error::what. No action necessary.

Issue 19: Should classes error_code and error_category be less-than comparable?

The Boost version provides operator<  for both, allowing objects of class error_code to be easily used as keys for associative containers. I can't remember any rationale for removing them from the proposal; it looks like a simple oversight.

Submitted by Beman Dawes.

Proposed resolution

See proposed wording for issue 7.

Issue 20: Which error_category applies to POSIX-like operating system errors unclear

For POSIX based operating systems (Unix, Linux, Mac OS, etc.) it isn't clear to either implementors or users whether operating system errors will be classified as native_category or posix_category.

Submitted by Beman Dawes.

Proposed resolution

No action necessary. This is essentially a FAQ (see below) that has to be dealt with by education. The issue 15 rationale may also be helpful.

FAQ

What error_category are the errors arising from POSIX-based operating systems such as Unix, Linux, Mac OS? They are system_category errors.

Issue 21: Error enums add lots of names to namespace std

Would it be better to move them into sub-namespaces?

Submitted by Chris Kohlhoff

Proposed resolution

Resolved by moving POSIX enums into a sub-namespace. See proposed wording for issue 7.

Rationale

These names should reflect the names assigned by the originating organization, such as the POSIX committee, so are somewhat beyond the control of the C++ committee. Also, name clashes with platform specific error enums occur in the absence of sub-namespaces. Experiments with real code show introduction of a posix sub-namespace improves readability.

Issue 22: Out of memory should throw bad_alloc, not system_error

If an out of memory condition occurs in a call to an operating system API from a standard library implementation, should the resulting exception be bad_alloc or system_error?

Proposed resolution

To 19.4.3.1 Class system_error overview [syserr.syserr.overview] add:

[Note: if an error represents an out-of-memory condition, implementations are encouraged to throw an exception of type bad_alloc ([bad.alloc]) rather than system_error. --end note]

Rationale

The LWG discussed this in Toronto, and wishes bad_alloc be thrown.

The proposed wording is in the form of a non-normative note because the type of exception throw on out of memory is implementation defined. See 17.4.4.8 Restrictions on exception handling [res.on.exception.handling]

Issue 23: error_category::name() should return const char *

The specification of error_category::name() as returning a constant string reference makes it too easy to implement in a non-thread-safe way such as a static function-scope variable. To be safe I think the function should return either a std::string by value or const char*. My preference would be for the latter, since it better reflects that what you are defining is a string constant that corresponds to the category.

Submitted by Chris Kohlhoff.

Proposed resolution

Change 19.4.1.1 Class error_category overview [syserr.errcat.overview] as indicated:

virtual const string& char* name() const = 0;

Issue 24: system_error what-less constructors needed

The problem of the what arg becomes more significant once you start composing operations. Since we're providing both throwing and non-throwing overloads, to reduce code duplication I like to implement the operation once in non-throwing form and then wrap it with the throwing version. For example:

  error_code download_to_directory(
      std::string dirname, std::string url, error_code& ec)
  {
    if (mkdir(dirname, ec))
      return ec;
  
    std::string host = url2host(url);
    tcp::endpoint ep = resolve(host, ec);
    if (ec) return ec;
  
    return download(dirname, ep, url2path(url), ec);
  }
  
  void download_to_directory(
      std::string dirname, std::string url)
  {
    error_code ec;
    download_to_directory(dirname, url, ec);
    if (ec) throw system_error(ec, ?);
  }  
The functions resolve() and download() are themselves composed operations, which may in turn use other composed operations. The original "what" of any error code is well and truly lost by the time you reach the throw.

Submitted by Chris Kohlhoff.

Beman Dawes comments: It is often very useful to users to know the name of the function where a system_error originates. Thus I would write the line in question like this:

if (ec) throw system_error(ec, "download_to_directory");

But that is a QOI issue, so I support adding constructors that do not require a what string.

Proposed resolution

Change 19.4.3.1 Class system_error overview [syserr.syserr.overview] as indicated:

class system_error : public runtime_error {
public:
  system_error(error_code ec, const string& what_arg);
  system_error(error_code ec);
  system_error(error_code::value_type ev, const error_category& ecat,
      const string& what_arg);
  system_error(error_code::value_type ev, const error_category& ecat);
  const error_code& code() const throw();
  const char* what() const throw();
};

To 19.4.3.2 Class system_error members [syserr.syserr.members] add:

system_error(error_code ec);

Effects: Constructs an object of class system_error.

Postconditions: code() == ec and strcmp(runtime_error::what(), "") == 0.

system_error(int ev, const error_category& ecat);

Effects: Constructs an object of class system_error.

Postconditions: code() == error_code(ev, ecat) and strcmp(runtime_error::what(), "") == 0.

Acknowledgements

Chris Kohlhoff, Benjamin Kosnik, and Peter Dimov provided much assistance in identifying and resolving issues.