c++c++11winapiposixstd-system-error

<system_error> categories and standard/system error codes


C++11 introduced the <system_error> header containing a generic system to handle error codes. An std::error_code is a tuple containing an int, the error code, and a reference to an std::error_category, which defines the error domain and handling of the error code. The standard library comes with four categories: std::generic_category, std::system_category, std::future_category, and std::iostream_category.

There are conflicts on which category to use, both here on SO and on C++ reference sites, when creating std::error_codes/throwing std::system_errors with errno and WinAPI error codes:

However, errno and GetLastError() can't use the same category, otherwise some error codes would be ambiguous. Error code 33 is one example, as it is both EDOM and ERROR_LOCK_VIOLATION.

There are even some places advocating a user-made category for the WinAPI, but I can't find any references to that at the moment. This alternative would be specially painful.

Which category should be used with errno, and which should be used with GetLastError() so that

are unambinguous and appropriate to the underlying error code?


Solution

  • In the C++ standard:

    system_category

    The current C++17 draft states that:

    Certain functions in the C ++ standard library report errors via a std::error_code (19.5.2.1) object. That object’s category() member shall return std::system_category() for errors originating from the operating system, or a reference to an implementation-defined error_category object for errors originating elsewhere. The implementation shall define the possible values of value() for each of these error > categories. [ Example: For operating systems that are based on POSIX, implementations are encouraged to define the std::system_category() values as identical to the POSIX errno values, with additional values as defined by the operating system’s documentation. Implementations for operating systems that are not based on POSIX are encouraged to define values identical to the operating system’s values. For errors that do not originate from the operating system, the implementation may provide enums for the associated values.

    It's not so clear:

    generic_category

    This means that POSIX error code can be used with generic_category. Non POSIX values might possibly not work correctly with generic_catgeory. In practice, they seem to be supported by the implementations I've been using.

    In Boost

    Boost system itself

    The Boost documentation is quite terse about this feature:

    The original proposal viewed error categories as a binary choice between errno (i.e. POSIX-style) and the native operating system's error codes.

    Moreover you can find legacy declaration such as:

    static const error_category & errno_ecat = generic_category();

    In linux_error.hpp:

    To construct an error_code after a API error: error_code( errno, system_category() )

    In windows_error.hpp:

    To construct an error_code after a API error: error_code( ::GetLastError(), system_category() )

    In cygwin_error.hpp:

    To construct an error_code after a API error: error_code( errno, system_category() )

    For Windows, Boost uses system_category for non errno errors:

    ec = error_code( ERROR_ACCESS_DENIED, system_category() );
    ec = error_code( ERROR_ALREADY_EXISTS, system_category() );
    ec = error_code( ERROR_BAD_UNIT, system_category() );
    ec = error_code( ERROR_WRITE_PROTECT, system_category() );
    ec = error_code( WSAEWOULDBLOCK, system_category() );
    

    In ASIO

    We find this kind of code in ASIO:

    template <typename ReturnType>
    inline ReturnType error_wrapper(ReturnType return_value,
        boost::system::error_code& ec)
    {
    #if defined(BOOST_ASIO_WINDOWS) || defined(__CYGWIN__)
      ec = boost::system::error_code(WSAGetLastError(),
          boost::asio::error::get_system_category());
    #else
      ec = boost::system::error_code(errno,
          boost::asio::error::get_system_category());
    #endif
      return return_value;
    }
    

    We find errno as system_category in POSIX code:

    int error = ::pthread_cond_init(&cond_, 0);
    boost::system::error_code ec(error,
        boost::asio::error::get_system_category());
    

    Filesystem

    We find errno with generic_category in POSIX code:

    if (::chmod(p.c_str(), mode_cast(prms)))
    {
      if (ec == 0)
        BOOST_FILESYSTEM_THROW(filesystem_error(
          "boost::filesystem::permissions", p,
          error_code(errno, system::generic_category())));
      else
        ec->assign(errno, system::generic_category());
    
    }
    

    In GNU libstdc++

    Filesystem

    We find errno with generic_category:

    if (char* rp = ::realpath(pa.c_str(), buf.get())) {
      [...]
    }
    if (errno != ENAMETOOLONG) {
      ec.assign(errno, std::generic_category());
      return result;
    }
    

    and no usage of system_category.

    Using libstdc++

    In practice, it seems you can use generic_category for non-POSIX errno with libstdc++:

    std::error_code a(EADV, std::generic_category());
    std::error_code b(EADV, std::system_category());
    std::cerr << a.message() << '\n';
    std::cerr << b.message() << '\n';
    

    Gives:

    Advertise error
    Advertise error
    

    Libc++

    We find errno with system_category:

    int ec = pthread_join(__t_, 0);
    if (ec)
      throw system_error(error_code(ec, system_category()), "thread::join failed");
    

    but no usage of generic_category.

    Conclusion

    I don't find any consistent pattern here but apparently:

    New proposals (Update 2019-12)

    There is a proposal to introduce a new error systems (std::error, std::status_code).

    See the relevant discussion and its section 4 for a discussion about the issues with the <system_error> facilities:

    • use of std::string
    • proliferation of "two-API" libraries
    • no wording sets aside the 0 enumerator
    • reliance on singletons
    • no error_category subclass can be a literal type
    • no guidance on attaching extra information to error_code
    • reliance on a surprising overload of operator==
    • error_category should properly have been named error_domain
    • standard error_code-yielding functions can throw exceptions anyway
    • underspecified error_code comparison semantics