Document Number

P1195R0

Date

2018-09-27

Project

Programming Language C++

Audience

Library Evolution Working Group

Summary

This paper adds constexpr to <system_error>.

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== and error_category::operator!= have been implemented as nonmembers, because a literal error_category requires compiler support for the not yet accepted P1077R0 (or, alternatively, for constexpr 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

(All edits are relative to N4762.)

Change [system_error.syn] as follows:

  • 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;

Change [syserr.errcat.overview] as follows:

  • 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;
    }

Change [syserr.errcat.virtuals] as follows:

  • 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;

Change [syserr.errcat.nonvirtuals] as follows:

  • constexpr bool operator==(const error_category& rhs) const noexcept;
    constexpr bool operator!=(const error_category& rhs) const noexcept;

Change [syserr.errcat.objects] as follows:

  • constexpr const error_category& generic_category() noexcept;
    constexpr const error_category& system_category() noexcept;

Change [syserr.errcode.overview] as follows:

  • 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);
    }

Change [syserr.errcode.constructors] as follows:

  • constexpr error_code() noexcept;
    constexpr error_code(int val, const error_category& cat) noexcept;
    template<class ErrorCodeEnum>
      constexpr error_code(ErrorCodeEnum e) noexcept;

Change [syserr.errcode.modifiers] as follows:

  • constexpr void assign(int val, const error_category& cat) noexcept;
    template<class ErrorCodeEnum>
      constexpr error_code& operator=(ErrorCodeEnum e) noexcept;
    constexpr void clear() noexcept;

Change [syserr.errcode.observers] as follows:

  • 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;

Change [syserr.errcode.nonmembers] as follows:

  • constexpr error_code make_error_code(errc e) noexcept;

Change [syserr.errcondition.overview] as follows:

  • 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
      };
    }

Change [syserr.errcondition.constructors] as follows:

  • constexpr error_condition() noexcept;
    constexpr error_condition(int val, const error_category& cat) noexcept;
    template<class ErrorConditionEnum>
      constexpr error_condition(ErrorConditionEnum e) noexcept;

Change [syserr.errcondition.modifiers] as follows:

  • constexpr void assign(int val, const error_category& cat) noexcept;
    template<class ErrorConditionEnum>
      constexpr error_condition& operator=(ErrorConditionEnum e) noexcept;
    constexpr void clear() noexcept;

Change [syserr.errcondition.observers] as follows:

  • constexpr int value() const noexcept;
    constexpr const error_category& category() const noexcept;
    constexpr explicit operator bool() const noexcept;

Change [syserr.errcondition.nonmembers] as follows:

  • constexpr error_condition make_error_condition(errc e) noexcept;

Change [syserr.compare] as follows:

  • 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