Document Number

P1198R0

Date

2018-09-28

Project

Programming Language C++

Audience

Library Evolution Working Group

Summary

This paper proposes that std::error_category::failed() be added, so that success or failure no longer be hardcoded to rely on whether the code value is zero.

Rationale

At present, the idiomatic pattern

void f(std::error_code& ec)
{
    perform_first_action(ec);
    if(ec) return;

    perform_second_action(ec);
    if(ec) return;

    // ...
}

tests for failure by using operator bool, which is hardcoded to return value() != 0. This makes it impossible to define categories for which the equivalence between 0 and success doesn’t hold. For instance, Windows HRESULT codes represent success when nonnegative, and HTTP status codes represent success when between 200 and 299.

Categories should encapsulate the knowledge of interpreting a code as a success or as a failure, and this paper proposes a mechanism by which they can do so, by defining a virtual member function failed.

In addition, failed() is added to error_code and error_condition, so that the idiomatic use can be

    if(ec.failed()) return;

Practice shows that if(ec) is often confusing, as it can be interpreted as both "has succeeded" and "has failed". No such ambiguity exists with if(ec.failed()).

Implementability

The proposed changes have been implemented in Boost.System and currently reside on its develop branch. They are expected to ship in Boost 1.69.

The Boost implementation calls category().failed(value()) in the constructor of error_code (and error_condition) and caches the result in a bool member. This results in significantly improved code generation because the category is almost always known at compile time at the point the constructor of error_code is invoked, whereas it is typically not known at the point error_code::failed is invoked.

On 64 bit platforms, the bool member can fit into the unused padding between int val_ and error_category const* cat_, so error_code doesn’t grow in size.

Proposed Changes

(All edits are relative to N4762.)

Change [syserr.errcat.overview] as follows:

  • namespace std {
      class error_category {
      public:
        constexpr error_category() noexcept;
        virtual ~error_category();
        error_category& operator=(const error_category&) = delete;
        virtual const char* name() const noexcept = 0;
        virtual error_condition default_error_condition(int ev) const noexcept;
        virtual bool equivalent(int code, const error_condition& condition) const noexcept;
        virtual bool equivalent(const error_code& code, int condition) const noexcept;
        virtual string message(int ev) const = 0;
        constexpr virtual bool failed(int ev) const noexcept;
    
        bool operator==(const error_category& rhs) const noexcept;
        bool operator!=(const error_category& rhs) const noexcept;
        bool operator< (const error_category& rhs) const noexcept;
      };
    
      const error_category& generic_category() noexcept;
      const error_category& system_category() noexcept;
    }

Add to [syserr.errcat.virtuals] as follows:

  • constexpr virtual bool failed(int ev) const noexcept;
    Returns:

    ev != 0.

Add to [syserr.errcat.derived] as follows:

  • virtual bool failed(int ev) const noexcept;
    Returns:

    true when ev represents a failure.

    Remarks:

    All calls passing the same ev shall yield the same result.

Change [syserr.errcode.observers] as follows:

  • explicit operator bool() const noexcept;
    Returns:

    value() != 0 failed().

    constexpr bool failed() const noexcept;
    Returns:

    category().failed(value()).

    [Note: The implementation may call category().failed(value()) once, for example at construction time, and cache the result. --end note]

Change [syserr.errcondition.observers] as follows:

  • explicit operator bool() const noexcept;
    Returns:

    value() != 0 failed().

    constexpr bool failed() const noexcept;
    Returns:

    category().failed(value()).

    [Note: The implementation may call category().failed(value()) once, for example at construction time, and cache the result. --end note]

ABI Implications

The proposed addition of a new virtual function to error_category unfortunately constitutes an ABI break.

It affects new code calling the new failed function on error_code objects from user-defined categories returned by old code. In addition, since the contextual conversion to bool is proposed to now call failed, code using this old construct will also be affected.

Adopting the versioning mechanism proposed in P1196R0 — if deemed workable — would solve both issues. Otherwise, if affecting just code using the new construct is considered acceptable, the bool conversion can be sacrificed on the altar of compatibility and left unchanged.

-- end