37

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

  • std::error_code::default_error_condition()
  • std::error_code::message()

are unambinguous and appropriate to the underlying error code?

einpoklum
  • 118,144
  • 57
  • 340
  • 684
moatPylon
  • 2,103
  • 1
  • 15
  • 22
  • 1
    The _real_ problem is that C++ standardization committee sometimes seems to ignore this: https://dailyzooniverse.files.wordpress.com/2014/04/gz-os-usage.png?w=788 and define certain things by considering just posix-derived interfaces. I doubt the standard will ever accommodate this. – Emilio Garavaglia Feb 26 '15 at 15:35
  • Honestly, don't use ``. It's poorly implemented, hard to use, and I'm really not sure why it was included into the standard. – Collin Dauphinee Feb 26 '15 at 20:08
  • 1
    How difficult would it be to make a `GetLastError()` category? The only two trouble spots I can think of are: a) defining the name (either you're going to have to have a million string constants for all those errors or you'll have to just do something generic like "error code nnn") and b) does `default_error_condition()` specify something to return for "no mapping"? because cppreference.com isn't saying – andlabs Feb 27 '15 at 01:15
  • 1
    `message()` at least has the courtesy to be a `std::string` and not a static buffer, so you could just call `FormatMessage()` at runtime and be done with it. Then you have the fun question of COM errors, where at least with C++ you can use `_com_error` and its `ErrorMessage()` method if you were worried about the documented-ness of `FormatMessage()` on a COM error, but names would still be an issue... and COM errors can encapsulate `GetLastError()` codes too... – andlabs Feb 27 '15 at 01:17

3 Answers3

23

I have to admit to a bit of surprise at the confusion regarding <system_error> given Chris summarised exactly how it works at http://blog.think-async.com/2010/04/system-error-support-in-c0x-part-1.html and I personally find the C++ standard text above perfectly clear. But to summarise in very succinct words:

If on POSIX:

generic_category => POSIX standard errno space

system_category => Local POSIX errno space (usually extends POSIX with proprietary errno codes). Use strerror() to expand codes into string descriptions returned by message().

In practice on POSIX both implementations are the same underneath and map the native errno space.

If on Windows:

generic_category => POSIX standard errno space which is returned by various POSIX emulation functions in the MSVCRT like fopen() etc

system_category => The Win32 GetLastError() space. Use FormatMessage() to expand codes into string descriptions returned by message().

How to use <system_error> portably

std::error_code ec;
#ifdef _WIN32
if((HANDLE)-1 == CreateFile(...))
  ec = std::error_code(GetLastError(), std::system_category());
#else
if(-1 == open(...))
  ec = std::error_code(errno, std::system_category());
#endif
// To test using portable code
if(ec == std::errc::no_such_file_or_directory)
   ...
// To convert into nearest portable error condition (lossy, may fail)
std::error_condition ec2(ec.default_error_condition())

Other thoughts:

Some commentators have said that <system_error> is poorly designed and shouldn't be used. This is simply not true, it's pretty optimal given the C++ 03 idiomatic practice of the time of its design, it generates very tight high quality fixed latency code on all major STLs except Dinkumware's. It's user extensible to any arbitrary error code system, and standardises unifying into a single system disparate third party library error handling.

It is true it would look quite different today had constexpr global variables been available at the time of its design, and maybe that might get rectified in a C++ standard coming after 17. But if you are a programmer who needs to move around error codes from third party libraries without losing information through code not written to know about those third party libraries, then <system_error> is an excellent solution.

Consider it as similar to the virtual keyword for third party library error code handling - it erases the need for code transporting third party codes from needing to understand those codes. If you have that problem in your code base - and most large code bases do - then absolutely you should be using <system_error> instead of whatever error code mapping or translation system you're currently using.

Niall Douglas
  • 9,212
  • 2
  • 44
  • 54
  • Using errno (e.g. EEXIST) + system_category() and then checking for `ec == std::errc::file_exists` does not work for me, however it seems to work if I use the generic_category() instead. See https://stackoverflow.com/questions/52384571/error-code-how-to-set-and-check-errno – Guillaume Papin Sep 18 '18 at 12:56
  • 2
    This is a bug recently fixed in latest GCC 6, 7 and 8 point releases. It'll work as you expect if you're on the latest point release. See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60555. – Niall Douglas Sep 19 '18 at 08:19
18

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:

  • what is supposed to happen to errno values on Windows?

  • is an errno from a POSIX call "originating from the operating system" or is this supposed to be restricted to non POSIX calls?

generic_category

  • std::errc is an enumeration with the same values as the C/POSIX EFOOBAR errors code;

    The value of each enum errc constant shall be the same as the value of the <cerrno> macro shown in the above synopsis. Whether or not the implementation exposes the <cerrno> macros is unspecified.

  • make_error_code(std::errc) generates an erro_code using generic_category

    error_code make_error_code(errc e) noexcept;

    Returns: error_code(static_cast<int>(e), 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:

  • you are expected to use system_category when using Windows error on Windows;

  • you can safely use generic_category for POSIX values of errno;

  • you are not supposed to be able to use std::generic_category for non-POSIX vales of errno (it might not work);

  • If you do not want to check if your errno value is a POSIX one: on POSIX-based systems you are expected to be able to use system_error with errno (strictly speaking the support for this is not mandated, only encouraged). on POSIX-based systems you can use system_error with errno.

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
ysdx
  • 8,889
  • 1
  • 38
  • 51
  • 3
    We can only blame Microsoft for defining two different and unrelated error numbering systems. They really dropped the ball on this long time ago. Since `errno` is based on `#define`s, they could have easily made the values compatible, but didn't. – rustyx Jun 03 '16 at 09:34
  • 3
    @rustyx, Well I tend to think that the real problem is in the C++ standard. Having different categories is meaningful: a lot of libraries and APIs have their own error numbers (OpenGL, OpenCL, Mach…) But I'm not so sure about the value of a generic `system_category` : we could just use `generic_category` for `errno` and system-specific (`windows_category`) categories for system-specific errors. – ysdx Jun 03 '16 at 09:48
  • 2
    Also true, but obviously `system_category` is for errors coming from the system, so it is natural to use it with `errno` codes, since POSIX system errors *are* the `errno` codes: *[...the integer variable **errno**, which is set by **system calls** and some library functions in the event of an error to indicate what went wrong.](http://man7.org/linux/man-pages/man3/errno.3.html)*. It is futile to require POSIX developers to use `generic_category` for that, even if that makes the code non-portable. So the standard is flawed (but primarily only with respect to Win32). – rustyx Jun 03 '16 at 10:15
  • 3
    @rustyx, Well it's not clear to me what is "the system" (and whether the system returns only a given set of error codes). What about a POSIX system built on top of a Mach system? You have Mach calls which returns Mach error codes and POSIX calls which returns errnos. – ysdx Jun 03 '16 at 10:48
  • 1
    I fail to see the inconsistency as stated, except maybe for GNU libstdc++. In all examples, `system_category` is used for system errors coming from `GetLastError` on Windows or `errno` on Unix-based systems and `generic_category` is exclusively used for POSIX error codes coming from `errno`. The fact that system error codes of POSIX-compliant systems com from `errno` and are numerically equal to the errors in `generic_category` is just a coincidence because the `generic_category` and the system use both the same standardized codes and because those systems also use `errno` to return errors. – André Sassi Oct 06 '16 at 17:07
  • 1
    @ysdx What you said is not true. People are supposed to use system error codes with `system_category`, ***and*** it will be comparable to `std::errc` constants. If using Visual C++ 2015 or later (available when you commented), it is true that `std::error_code(ERROR_FILE_NOT_FOUND, std::system_category()) == std::errc::no_such_file_or_directory` *and* `std::error_code(ERROR_PATH_NOT_FOUND, std::system_category()) == std::errc::no_such_file_or_directory`. – Yongwei Wu Sep 23 '17 at 13:46
  • @YongweiWu: agreed. – ysdx Sep 23 '17 at 14:59
  • This is whole thing is so miserably over designed. – John Z. Li Oct 24 '18 at 02:37
1

Such confusion here !

An error category is a source of errors. The iostreams library produces its own errors, so it has its own category. Similarly WinAPI is its own source of errors, and so REQUIRES a user-defined error category.

generic_category() and system_category() are both for errno values. The distinction between them is based on the value of errno:

  • values specified by POSIX are used with generic_category() to create error_conditions that are portable
  • values with a POSIX equivalent are translated and used with generic_category()
  • values provided by the operating system implementation, outside of the POSIX values, are used with system_category() -- to create error_codes, which are not portable

The <system_error> library is built around the (implied) distinction between error_code and error_condition. The two classes have the same methods and same interfaces, except:

  • error_code objects are for "low level" and system-specific errors
  • error_condition objects are portable errors, for example defined by a standard
  • they use different error_category objects, since their numerical values get translated to different error strings.
  • you can try to map a system-specific error_code to a portable error_condition with the default_error_condition() method, on either the error_code or its error_category.

However when there is no translation from the error_code to the error_condition (or none is implemented) you get an error_condition based on a system-specific error_category, which already defeats the purpose of a portable mapping. Such is the state of the C++ standard library, I guess ... !

error_category objects are aware if they represent portable values or non-portable values. system_category() identifies system-specific values (of errno) and translates them to the POSIX values (if available), and then uses the generic_category() to map such values to an error_condition.

Every category knows to translate the numeric error into a string (description). system_category() translates specific ęrrno values to strings, most likely using standard strerror() or sys_errlist.

It follows that using WinAPI values from ::GetLastError() with this category is a programming error.

Using WinSock2 API for example, that provides error values from ::WSAGetLastError() function, requires yet another error category.

Toughy
  • 767
  • 6
  • 5