Doc. no. N2422=07-0282
Date:
2007-10-05
Project: Programming Language C++
Reply to: Beman Dawes <bdawes@acm.org>
Christopher Kohlhoff <chris@kohlhoff.com>
Introduction
Development history
Revision history
Issues
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
Rationale: Unintended consequences
Rationale: Motivation and use for error categories, codes,
and conditions
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
Issue 25: system_category
order-of-initialization problems
Acknowledgements
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 and Chris Kohlhoff. The issue submitters do not necessarily agree with these PR's.
N2241, Diagnostics Enhancements for C++0x (Rev. 1), provides introduction,
design rationale, and history for the diagnostic enhancements as added to the
C++ Standard Library at the Oxford C++ committee meeting in the spring of 2007.
In adopting the proposal, the committee's Library Working Group made a major
change by replacing constants of class error_code
with enumerated
values. This change broke a number of critical use cases, and so has forced a
redesign of many details. The overall picture remains the same, and most
end-user existing code is preserved. The detailed scrutiny that followed
uncovered various issues resulting in a number of improvements and
clarifications.
All of the proposed resolutions contained in this paper have been implemented in the Boost library, and are being used by early adopters.
This paper is a minor revision of N2415, Diagnostics Issues (Rev. 1). Issues 25 and 26 were added. Missing wording for a portion of issue 7 was supplied. Minor typos and errors were corrected. Additional rationale was added. Additional Development history was added.
N2415 was a major revision of N2296, Diagnostics Enhancements; Resolution of Small Issues. The name was changed to reflect the wider scope of several proposals.
The clear()
function is shown in the synopsis but is otherwise unspecified.
Reported separately by Pete Becker, PJ Plauger, and Benjamin Kosnik.
Specify the semantics. See proposed wording for issue 7.
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
Change the form of enum posix_errno
in 19.4 System error
support [syserr] as indicated:
enum posix_errno {
... Apply same pattern of changes to all remaining constants for the enum.
address_family_not_supported= EAFNOSUPPORT, // EAFNOSUPPORT
address_in_use= EADDRINUSE, // EADDRINUSE
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.
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.
In 19.4.2.1 Class error_code overview [syserr.errcode.overview], remove:
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
,andstd::wstring
, andstd::error_code
.
Submitted by Alisdair Meredith.
See proposed wording for issue 7.
The conversion constructor is important design element; without it error enum ease-of-use suffers badly. Removing the conversion constructor would force yet another redesign.
The specific concern was that the constructor may cause implicit conversion
from an integer to an error_code
. The proposed resolution for
issue 7 addresses this issue.
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.
Take no action at this time.
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.
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.
Make error_category non-copyable. See proposed wording for issue 7.
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:
ec == windows_error::access_denied
.ec == windows_error::access_denied
to test
for a system-specific error, but have to write ec.posix() ==
posix_error::permission_denied
to get the portable equivalent, it is both
error prone and discourages portable code. 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 : public false_type {}; template< class T > struct is_error_condition_enum : public false_type {}; namespace posix_error { 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_error::posix_errno> : public true_type {}; namespace posix_error { error_code make_error_code(posix_errno e); error_condition make_error_condition(posix_errno e); }bool operator==(const error_code& lhs, const error_code& rhs); bool operator==(const error_code&eccode,posix_errno enconst error_condition& condition); bool operator==(posix_errno enconst error_condition& condition, const error_code&eccode); 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&eccode,posix_errno enconst error_condition& condition); bool operator!=(posix_errno enconst error_condition& condition, const error_code&eccode); 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); } // namespace stdUsers may specialize
is_error_code_enum
, andis_error_condition_enum
templates to indicate that a type is eligible forclass error_code
anderror_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 conststring&char* 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; }; const error_category& get_posix_category(); const error_category& get_system_category();externstatic const error_category& posix_category = get_posix_category();externstatic const error_category&native_categorysystem_category = get_system_category(); } // namespace std
Change 19.4.1.2 Class error_category virtual members [syserr.errcat.virtuals] as indicated:
virtual const
char*string&name() const = 0;
Returns: A string naming the error category.
Throws: Nothing.
virtual posix_errno posix(error_code::value_type ev) const = 0;
virtual error_condition default_error_condition(int ev) const;
Returns:
A value of typeposix_errno
that corresponds toev
if such a corresponding POSIX error number exists, otherwiseothererror_condition(ev, *this)
.[ Note: Since the possible values ofev
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":
virtual const char* name() const = 0;
Returns: A string naming the error category.
Throws: Nothing.
virtual error_condition default_error_condition(int ev) const;
Returns: An object of type
error_condition
that corresponds toev
.Throws: Nothing.
virtual bool equivalent(int code, const error_condition & condition) const;
Returns:
true
, if for the category of error represented by*this
,code
is considered equivalent tocondition
. Otherwise,false
.Throws: Nothing.
virtual bool equivalent(const error_code & code, int condition) const;
Returns:
true
, if for the category of error represented by*this
,condition
is considered equivalent tocode
. Otherwise,false
..Throws: Nothing.
Replace 19.4.1.4 Error category objects [syserr.errcat.objects] with:
const error_category& get_posix_category();
Returns: A reference to an object of a type derived from class
error_category
.Remarks: The object's
default_error_condition
andequivalent
virtual functions shall behave as specified for classerror_category
. The object'sname
virtual function shall return a pointer to the string"POSIX"
.const error_category& get_system_category();
Returns: A reference to an object of a type derived from class
error_category
.Remarks: The object's
equivalent
virtual functions shall behave as specified for classerror_category
. The object'sname
virtual function shall return a pointer to the string"system"
. The object'sdefault_error_condition
virtual function shall behave as follows.If the argument
ev
corresponds to a POSIXerrno
valueposv
, returnerror_condition(posv, posix_category)
. Otherwise, returnerror_condition(ev, system_category)
. What constitutes correspondence for any given operating system is unspecified. [Note: The number of potential system error codes is large and unbounded, and some may not correspond to any POSIXerrno
value. Thus implementations are given latitude in determining correspondence. --end note]
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_typeint val, const error_category& cat);error_code(posix_errno val);template <classErrorCodeEnum
> error_code(ErrorCodeEnum
e, typename enable_if<is_error_code_enum<ErrorCodeEnum
> >::type* = 0); // modifiers: void assign(value_typeint val, const error_category& cat);error_code& operator=(posix_errno val);template<typenameErrorCodeEnum
> typename enable_if<is_error_code_enum<ErrorCodeEnum
>, error_code>::type & operator=(ErrorCodeEnum
e );; void clear(); // observers:value_typeint 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_typeint 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
andcat_ ==
.posix_category&system_categoryThrows: Nothing.
error_code(
value_typeint val, const error_category& cat);Effects: Constructs an object of type
error_code
.Postconditions:
val_ == 0
andcat_ == cat
.Throws: Nothing.
template <classerror_code(posix_errno val);
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(e)
.Throws: Nothing.
Change 19.4.2.3 Class error_code modifiers [syserr.errcode.modifiers] as indicated:
error_code(
value_typeint val, const error_category& cat);Postconditions:
val_ == val
andcat_ == cat
.Throws: Nothing.
template<typenameerror_code& operator=(posix_errno val);
ErrorCodeEnum
> typename enable_if<is_error_code_enum<ErrorCodeEnum
>, error_code>::type & operator=(ErrorCodeEnum
e );Postconditions:
val_ == static_cast<value_type>(val) and cat_ == posix_category*this == make_error_code(e)
.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, and change the sub-section title to "Non-member functions" or similar:
error_code make_error_code(posix_errno e);Returns:
error_code(e, posix_category)
.error_condition make_error_condition(posix_errno e);Returns:
error_condition(e, posix_category)
.
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&
eccode,posix_errno enconst error_condition& condition);
bool operator==(posix_errno enconst error_condition& condition, const error_code&eccode);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&eccode,posix_errno enconst error_condition& condition); bool operator!=(posix_errno enconst error_condition& condition, const error_code&eccode);Returns:!(
eccode
==
encondition
).
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.
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, whileerror_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 e ); 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 std19.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(e)
.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 e );;Postconditions:
*this == make_error_condition(e)
.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 evaluatetrue
in a boolean context; otherwise, returns a value that will evaluatefalse
. The return type shall not be convertible toint
.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., toint
) that can occur withbool
are not allowed, eliminating some sources of user error. One possible implementation choice for this type is pointer to member. --end note ]
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.
// netdb header ---------------------------------------------------------------- namespace netdb_errors { enum code { host_not_found = HOST_NOT_FOUND, try_again = TRY_AGAIN, no_recovery = NO_RECOVERY, no_data = NO_DATA }; const error_category& get_category(); static const error_category& category = get_category(); inline error_code make_error_code(code e) { return error_code(static_cast<int>(e), category); } } namespace std { template <> struct is_error_code_enum<netdb_errors::code> : public true_type {}; } // netdb source ---------------------------------------------------------------- namespace netdb_errors { class category_impl : public error_category { public: const char* name() const { return "netdb"; } }; const error_category& get_category() { static category_impl c; return c; } } // addrinfo header ------------------------------------------------------------- namespace addrinfo_errors { enum code { again = EAI_AGAIN, bad_flags = EAI_BADFLAGS, fail = EAI_FAIL, family = EAI_FAMILY, memory = EAI_MEMORY, no_name = EAI_NONAME, service = EAI_SERVICE, socket_type = EAI_SOCKTYPE, overflow = EAI_OVERFLOW }; const error_category& get_category(); static const error_category& category = get_category(); inline error_code make_error_code(code e) { return error_code(static_cast<int>(e), category); } } namespace std { template <> struct is_error_code_enum<addrinfo_errors::code> : public true_type {}; } // addrinfo source ------------------------------------------------------------- namespace addrinfo_errors { class category_impl : public error_category { public: const char* name() const { return "addrinfo"; } }; const error_category& get_category() { static category_impl c; return c; } } // resolve header -------------------------------------------------------------- namespace resolve { enum condition { host_not_found = 1, try_again }; const error_category& get_category(); static const error_category& category = get_category(); inline error_condition make_error_condition(condition e) { return error_condition(static_cast<int>(e), category); } string get_fqdn(string host); string get_fqdn(string host, error_code& ec); } namespace std { template <> struct is_error_condition_enum<resolve::condition> : public true_type {}; } // resolve source -------------------------------------------------------------- namespace resolve { class category_impl : public error_category { public: const char* name() const { return "resolve"; } bool equivalent(const error_code& code, int condition) { switch (condition) { case host_not_found: return code == netdb_errors::host_not_found || code == addrinfo_errors::no_name; case try_again: return code == netdb_errors::try_again || code == addrinfo_errors::again; default: return false; } } }; const error_category& get_category() { static category_impl c; return c; } string get_fqdn(string host) { error_code ec; string s = get_fqdn(host, ec); if (ec) throw system_error(ec, "get_fqdn"); return s; } #if defined(USE_NETDB) string get_fqdn(string host, error_code& ec) { if (hostent* h = gethostbyname(host.c_str())) { ec.clear(); return h->h_name; } else { ec.assign(h_errno, netdb_errors::category); return string(); } } #else string get_fqdn(string host, error_code& ec) { addrinfo hints = addrinfo(); hints.ai_flags = AI_CANONNAME; addrinfo* result; if (int err = getaddrinfo(host.c_str(), "", &hints, &result)) { if (err == EAI_SYSTEM) ec.assign(errno, system_category); else ec.assign(err, addrinfo_errors::category); return string(); } else { ec.clear(); string fqdn = result->ai_canonname; freeaddrinfo(result); return fqdn; } } #endif } // ----------------------------------------------------------------------------- #include <iostream> #include <ostream> int main(int argc, char* argv[]) { if (argc != 2) return 1; error_code ec; string fqdn = resolve::get_fqdn(argv[1], ec); if (ec) { if (ec == resolve::host_not_found) cout << "Host not found" << endl; else if (ec == resolve::try_again) cout << "Try again later" << endl; else cout << "Some other error: " << ec.value() << endl; } else { cout << fqdn << endl; } try { cout << resolve::get_fqdn(argv[1]) << endl; } catch (exception& e) { cerr << e.what() << endl; } }
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.
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 thaterrno
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.
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.
Take no action at this time. The issue will be left open to see if a solution can be found.
Discussed in Kona. LWG members sympathised with the problem, but did not
wish to see a requirement that the ostream
header include
the system_error
header either.
error_code::value_type
with circular dependenciesSection 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.
Change the type to int. See proposed wording for issue 7.
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.
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.
No action necessary.
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. LWG members noted that type
traits may be used to detect empty throw specifications, and so this may be revisited
in a future cleanup of this inconsistency across the whole library.
system_error
constructorAll 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 aconst std::string&
argument.
No action necessary.
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".
error_category
equality via
address comparisonSection 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.
No action necessary.
Resolved by Issue 6: Class error_category should be non-copyable. No further action needed.
error_code::value_types
constants and
user-defined error_category
objectsSection 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.
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'scategory()
member shall return a reference tostd::system_category
for errors originating from the operating system, or a reference to an implementation-definederror_category
object for errors originating elsewhere. The implementation shall define the possible values ofvalue()
for each of these error categories. [Example: For a POSIX-based operating system, an implementation is encouraged to define thestd::system_category
values as identical to the POSIXerrno
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]
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. 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<cygwin::cygwin_errno> { static const bool value = true; }; namespace cygwin { inline error_code make_error_code(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<Linux::linux_errno> { static const bool value = true; }; namespace Linux { inline error_code make_error_code(linux_error e) { return error_code( e, system_category ); } } #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<windows::windows_error> { static const bool value = true; }; namespace windows { inline error_code make_error_code(windows_error e) { return error_code( e, system_category ); } } #else # error BOOST_POSIX_API or BOOST_WINDOWS_API must be defined #endif
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 equivalenterror_code
anderror_condition
member functions, allowing user-defined error categories. This mechanism unravels iferror_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.
Change the name native_category
to system_category
.
See proposed wording for issue 7.
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.
error_category::message
member functionsSection 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.
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.
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.
At the Kona meeting, the LWG resolved to remove the wide character message functions and all reference to locales.
system_error::what
and constructorsIn 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.
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.
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.
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.
Add operator<
. See proposed wording for issue 7.
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.
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.
What error_category are the errors arising from POSIX-based operating
systems such as Unix, Linux, Mac OS? They are system_category
errors.
Would it be better to move them into sub-namespaces?
Submitted by Chris Kohlhoff
Resolved by moving POSIX enums into a sub-namespace. See proposed wording for issue 7.
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.
The LWG indicated that the namespace name "posix" should be reserved for future use, and the name "posix_error" was proposed as an alternative without any objections.
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
?
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 thansystem_error
. --end note]
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]
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.
Change the return to const char*
. See proposed wording for issue 7.
system_error
what-less
constructors neededThe 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:
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.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, ?); }
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.
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
andstrcmp(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)
andstrcmp(runtime_error::what(), "") == 0
.
system_category
order-of-initialization problemsSupplying system_category
and posix_category
predefined objects as extern const refs causes order-of-initialization
problems, depending on the exact order in which dynamic initialization occurs.
There are two aspects of this problem:
error_category
(and classes derived from it) objects
are dynamically initialized, because of the virtual members. Therefore there
needs to be a guarantee that the category objects are fully constructed when
accessed from constructors for global objects. (Chris Kohlhoff).Reported by David Deakins and Christopher Kohlhoff.
Add explicit dynamic initialization. See proposed wording for issue 7.
Chris Kohlhoff, Benjamin Kosnik, and Peter Dimov provided much assistance in identifying and resolving issues.