Document Number |
P1195R0 |
Date |
2018-09-27 |
Project |
Programming Language C++ |
Audience |
Library Evolution Working Group |
Summary |
This paper adds |
Summary
This paper proposes the addition of the constexpr
specifier to error_category
, error_code
and error_condition
,
as appropriate. That error_code
needs to be made constexpr
was first suggested in
LWG Issue 2992, which was closed as a feature request needing a paper.
Motivation
Consistency
Making the facilities provided by <system_error>
usable in constant expressions is consistent with the general
trend of making the standard library usable in constant expressions.
Performance
Error reporting via error_code
is a good fit for performance sensitive, resource constrained environments, where cycles
count. Making the functions creating and manipulating error_code
constexpr
ensures that they are fully visible to the
compiler and that they can be performed at compile time, if appropriate. This in practice results in significantly improved
code generation.
For example, the function
void f( std::error_code & ec )
{
ec.clear();
}
on one popular implementation results in a call to _Execute_once
(to initialize the magic static returned by system_category
),
whereas with the proposed additions it compiles down to two mov
instructions.
Implementability
The suggested additions have largely been implemented and shipped in Boost.System release 1.68. The differences are as folllows:
-
Virtual functions have not been marked
constexpr
in the Boost implementation, as this requires compiler support for P1064R0. (P1064 has been voted into C++20 at Rapperswil, but no compiler implements it yet at time of writing.) -
The nonvirtual functions that are implemented in terms of virtual calls have not been marked
constexpr
. -
error_category::operator==
anderror_category::operator!=
have been implemented as nonmembers, because a literalerror_category
requires compiler support for the not yet accepted P1077R0 (or, alternatively, forconstexpr
destructors as in P0784R4.)
Boost.System 1.68 can be used as a static library, as a shared library, or as header-only, and the constexpr
additions work in all
three modes.
A test demonstrating the functionality is available on Github.
Proposed Changes
-
class error_category; constexpr const error_category& generic_category() noexcept; constexpr const error_category& system_category() noexcept;
-
// 18.5.5, comparison functions constexpr bool operator==(const error_code& lhs, const error_code& rhs) noexcept; constexpr bool operator==(const error_code& lhs, const error_condition& rhs) noexcept; constexpr bool operator==(const error_condition& lhs, const error_code& rhs) noexcept; constexpr bool operator==(const error_condition& lhs, const error_condition& rhs) noexcept; constexpr bool operator!=(const error_code& lhs, const error_code& rhs) noexcept; constexpr bool operator!=(const error_code& lhs, const error_condition& rhs) noexcept; constexpr bool operator!=(const error_condition& lhs, const error_code& rhs) noexcept; constexpr bool operator!=(const error_condition& lhs, const error_condition& rhs) noexcept; bool operator< (const error_code& lhs, const error_code& rhs) noexcept; bool operator< (const error_condition& lhs, const error_condition& rhs) noexcept;
-
namespace std { class error_category { public: constexpr error_category() noexcept; virtual ~error_category() = default; error_category(const error_category&) = delete; error_category& operator=(const error_category&) = delete; virtual const char* name() const noexcept = 0; constexpr virtual error_condition default_error_condition(int ev) const noexcept; constexpr virtual bool equivalent(int code, const error_condition& condition) const noexcept; constexpr virtual bool equivalent(const error_code& code, int condition) const noexcept; virtual string message(int ev) const = 0; constexpr bool operator==(const error_category& rhs) const noexcept; constexpr bool operator!=(const error_category& rhs) const noexcept; bool operator< (const error_category& rhs) const noexcept; }; constexpr const error_category& generic_category() noexcept; constexpr const error_category& system_category() noexcept; }
-
virtual ~error_category();
Effects: Destroys an object of class error_category.
constexpr virtual error_condition default_error_condition(int ev) const noexcept;
constexpr virtual bool equivalent(int code, const error_condition& condition) const noexcept;
constexpr virtual bool equivalent(const error_code& code, int condition) const noexcept;
-
constexpr bool operator==(const error_category& rhs) const noexcept;
constexpr bool operator!=(const error_category& rhs) const noexcept;
-
constexpr const error_category& generic_category() noexcept;
constexpr const error_category& system_category() noexcept;
-
namespace std { class error_code { public: // 18.5.3.2, constructors constexpr error_code() noexcept; constexpr error_code(int val, const error_category& cat) noexcept; template<class ErrorCodeEnum> constexpr error_code(ErrorCodeEnum e) noexcept; // 18.5.3.3, modifiers constexpr void assign(int val, const error_category& cat) noexcept; template<class ErrorCodeEnum> constexpr error_code& operator=(ErrorCodeEnum e) noexcept; constexpr void clear() noexcept; // 18.5.3.4, observers constexpr int value() const noexcept; constexpr const error_category& category() const noexcept; constexpr error_condition default_error_condition() const noexcept; string message() const; constexpr explicit operator bool() const noexcept; private: int val_; // exposition only const error_category* cat_; // exposition only }; // 18.5.3.5, non-member functions constexpr error_code make_error_code(errc e) noexcept; template<class charT, class traits> basic_ostream<charT, traits>& operator<<(basic_ostream<charT, traits>& os, const error_code& ec); }
-
constexpr error_code() noexcept;
constexpr error_code(int val, const error_category& cat) noexcept;
template<class ErrorCodeEnum> constexpr error_code(ErrorCodeEnum e) noexcept;
-
constexpr void assign(int val, const error_category& cat) noexcept;
template<class ErrorCodeEnum> constexpr error_code& operator=(ErrorCodeEnum e) noexcept;
constexpr void clear() noexcept;
-
constexpr int value() const noexcept;
constexpr const error_category& category() const noexcept;
constexpr error_condition default_error_condition() const noexcept;
constexpr explicit operator bool() const noexcept;
-
constexpr error_code make_error_code(errc e) noexcept;
-
namespace std { class error_condition { public: // 18.5.4.2, constructors constexpr error_condition() noexcept; constexpr error_condition(int val, const error_category& cat) noexcept; template<class ErrorConditionEnum> constexpr error_condition(ErrorConditionEnum e) noexcept; // 18.5.4.3, modifiers constexpr void assign(int val, const error_category& cat) noexcept; template<class ErrorConditionEnum> constexpr error_condition& operator=(ErrorConditionEnum e) noexcept; constexpr void clear() noexcept; // 18.5.4.4, observers constexpr int value() const noexcept; constexpr const error_category& category() const noexcept; string message() const; constexpr explicit operator bool() const noexcept; private: int val_; // exposition only const error_category* cat_; // exposition only }; }
-
constexpr error_condition() noexcept;
constexpr error_condition(int val, const error_category& cat) noexcept;
template<class ErrorConditionEnum> constexpr error_condition(ErrorConditionEnum e) noexcept;
-
constexpr void assign(int val, const error_category& cat) noexcept;
template<class ErrorConditionEnum> constexpr error_condition& operator=(ErrorConditionEnum e) noexcept;
constexpr void clear() noexcept;
-
constexpr int value() const noexcept;
constexpr const error_category& category() const noexcept;
constexpr explicit operator bool() const noexcept;
-
constexpr error_condition make_error_condition(errc e) noexcept;
-
constexpr bool operator==(const error_code& lhs, const error_code& rhs) noexcept;
constexpr bool operator==(const error_code& lhs, const error_condition& rhs) noexcept;
constexpr bool operator==(const error_condition& lhs, const error_code& rhs) noexcept;
constexpr bool operator==(const error_condition& lhs, const error_condition& rhs) noexcept;
constexpr bool operator!=(const error_code& lhs, const error_code& rhs) noexcept; constexpr bool operator!=(const error_code& lhs, const error_condition& rhs) noexcept; constexpr bool operator!=(const error_condition& lhs, const error_code& rhs) noexcept; constexpr bool operator!=(const error_condition& lhs, const error_condition& rhs) noexcept;
Potential Objections
Immortal Categories
In order for error_code
to be usable during process exit, some implementations "immortalize" the category
objects by placement-constructing them so that their destructors are never run. (On Clang, the same effect can
be achieved by the attribute [[clang::no_destroy]]
.)
It’s possible to implement "immortalization" in a constexpr
-friendly way by using a union
instead of placement
new
, and the aforementioned LWG Issue 2992 has a code snippet that
shows the technique; but if P1077R0 is accepted, making the destructor of error_category
trivial, this shouldn’t even be necessary, as the standard category objects will not need destruction at all.
Duplicate Categories
To support scenarios in which more than one instance of a standard category is present in a process, some
implementations maintain "virtual addresses" for the standard categories, known constants that they use for
category equality comparisons instead of the real address. For user-defined categories, the "virtual address"
is derived from the real address via reinterpret_cast
, an operation that is constexpr
-hostile.
It’s possible to rework this scheme in a constexpr
-friendly way, but the companion paper
P1196R0 proposes an even better solution.
ABI Implications
The author believes that this proposal does not constitute an ABI break. In a typical implementation,
generic_category
changes from a declaration in <system_error>
:
const error_category& generic_category() noexcept;
to an inline definition along the lines of:
constexpr const error_category& generic_category() noexcept
{
extern const __generic_category_impl __generic_category_instance;
return __generic_category_instance;
}
but the implementation can still provide an out of line definition of generic_category
in the library
for old clients that link to it.
-- end