Document Number

P1197R0

Date

2018-09-27

Project

Programming Language C++

Audience

Library Evolution Working Group

Summary

This paper proposes a non-allocating overload of std::error_category::message, modeled after the Glibc-specific strerror_r interface.

Rationale

The facilities in <system_error> are suitable for reporting errors in resource-constrained environments, but the function message, specified to return an std::string, mandates allocation, which may be prohibited.

Therefore, an overload of message is proposed that returns the message into a caller-supplied buffer.

The function’s interface is modeled after the Glibc-specific strerror_r function, as it allows literals to be directly returned without a string copy or truncation.

The default error_category implementation of the new overload calls the existing message(ev) and returns the string into the supplied buffer, in order to retain backward compatibility.

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.

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;
        virtual const char* message(int ev, char* buffer, size_t len) 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:

  • virtual const char* message(int ev, char* buffer, size_t len) const noexcept;
    Effects:
    • If len is 0, returns buffer;

    • Otherwise, if message(ev) doesn’t throw, copies the string returned by message(ev) into buffer, truncating it to len-1 characters and adding a null terminator, and returns buffer;

    • Otherwise, either returns a pointer to an implementation-defined string literal, or copies an implementation-defined message into buffer, truncating it to len-1 characters and adding a null terminator, and returns buffer.

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

  • virtual const char* message(int ev, char* buffer, size_t len) const noexcept;
    Effects:
    • If a character literal that describes ev is available, returns a pointer to it;

    • Otherwise, if len is 0, returns buffer;

    • Otherwise, copies a message describing ev into buffer, truncating it to len-1 characters and adding a null terminator, and returns buffer.

      [Note: message(ev, nullptr, 0) is allowed. --end note]

      [Note: If the function returns a pointer distinct from buffer, the caller may assume that it points to a character literal. --end note]

      [Example:

      const char* my_category::message(int ev, char* buffer, size_t len) const noexcept
      {
        switch(ev)
        {
        case 0: return "no error";
        case 1: return "voltage out of range";
        case 2: return "impedance mismatch";
        case 31:
        case 32:
        case 33:
          std::snprintf(buffer, len, "component %d failure", ev-30);
          return buffer;
        default:
          std::snprintf(buffer, len, "unknown error %d", ev);
          return buffer;
        }
      }

      --end example]

Add to [syserr.errcode.observers] as follows:

  • const char* message(char* buffer, size_t len) const noexcept;
    Returns:

    category().message(value(), buffer, len).

Add to [syserr.errcondition.observers] as follows:

  • const char* message(char* buffer, size_t len) const noexcept;
    Returns:

    category().message(value(), buffer, len).

ABI Implications

The proposed addition of a new virtual function to error_category unfortunately constitutes an ABI break, although its impact is relatively limited, as it only affects new code calling the new message overload on error_code objects from user-defined categories returned by old code.

Adopting the versioning mechanism proposed in P1196R0, if deemed workable, could allow even the above scenario.

-- end