36

My question: What is the correct way to construct std::error_code instances from errno values on POSIX and GetLastError() on Windows so that the instances can be compared to the well-known values from std::errc?

The longer explanation: My goal is to add an std::error_code instance to a self-made exception object that works on POSIX and Windows systems in a C++11ish way.

In my cross-platform application I'm using a self-made I/O class hierarchy that uses the POSIX fopen() and Windows' CreateFile() calls for opening/creating files. If that fails a generic, self-made open_error exception is thrown (it is derived from std::exception, yes, but it's not one of C++'s predefined exception classes). I'm trying to extend this rather bare-bones exception with an error code; to be more precise with C++11's std::error_code if I understood correctly.

My problem is how to construct such an object from errno (in the POSIX case) or GetLastError() (in the Windows case). For POSIX, as far as I've understood things, I can simply use errno in std::error_code's constructor, e.g. like this:

std::error_code ec(errno, std::generic_category()); 

And that ec should be comparable to the well-known values from std::errc.

For Windows a similar call can be made, of course:

std::error_code ec(::GetLastError(), std::generic_category()); 

But I'm not sure whether or not the values returned by GetLastError() map nicely to the well-known constants from std::errc. I've read in Boost's system library that they do for Boost's implementation of error_code, but I'm asking about the std implementation, not about Boost's.

Please don't advice to switch to using C++ streams for file access. I'd love to, but refactoring half of my code is not something I'd like to do right at this very moment.

3 Answers 3

12

That's a quality of implementation issue. The const static object returned by std::system_category() is relied upon to perform the mapping from the platform-native error code enumeration to the standard std::error_condition enumeration. Under 17.6.5.14 Value of error codes [value.error.codes]:

Implementations for operating systems that are not based on POSIX are encouraged to define values identical to the operating system’s values.

You can see in http://www.boost.org/doc/libs/1_46_1/libs/system/src/error_code.cpp how Boost performs the mapping; any standard library supplied by your compiler vendor for use on Windows should do something similar.

The intended behaviour is covered in 19.5.1.5p4, describing system_category().default_error_condition(int ev):

If the argument ev corresponds to a POSIX errno value posv, the function shall return error_condition(posv, generic_category()). Otherwise, the function shall return error_condition(ev, system_category()).

So, for example, error_code(ERROR_FILE_NOT_FOUND, std::system_category()).default_error_condition() will invoke std::system_category().default_error_condition(ERROR_FILE_NOT_FOUND), which should return std::error_condition(std::no_such_file_or_directory, std::generic_category()).

Sign up to request clarification or add additional context in comments.

6 Comments

So my platform-conscious I/O class can construct with error_code(::GetLastError(), std::generic_category()), the platform-agnostic client code can check with e.g. if (std::errc::no_such_file_or_directory == caught_exception.code()), and it should just work, even on Windows, assuming vendors actually follow the encouragement of 17.6.5.14?
You should be using error_code(::GetLastError(), std::system_category()), not generic_category().
I've actually tried this with std::system_category() and errno on Linux with g++ 4.7.2, but in that case the comparison failed. With std::generic_category() it worked just fine.
Ah I see your edit. So on POSIX I should construct with error_code(errno, std::generic_category()) and on Windows I should use error_code(::GetLastError(), std::system_category())?
Yes, that appears to be a bug in gcc (well, in libstdc++); per 1.9.5.1p4 it should map POSIX error codes to generic_category. Your proposed workaround sounds fine for now.
|
7

This is an old question, but I haven't really found a good answer on SO. The accepted answer confuses me a little as it seems to give an error_condition rather than an error_code. I have settled on the following for myself on POSIX:

std::error_code error_code_from_errno(int errno_code) { return std::make_error_code(static_cast<std::errc>(errno_code)); } 

This always gives me the correct category (generic or system). I've had problems in the past where error codes with the same errno code compared as not equal because one had generic_category and the other had system_category.

2 Comments

This will not work on windows for values obtained from GetLastError(). make_error_code uses std::generic_category(), while GetLastError() needs std::system_category().
@MHebes make_error_code uses whatever the appropriate overload of make_error_code is implemented with. The point is that the static_cast needs to be the correct type.
3

It looks like you should use system_category() for GetLastError()/errno and it will do the right thing on both platforms.

If you already have an errc, use generic_category() (or make_error_code) instead.

Here's some testing with an "address already in use" error.

#include <iostream> #include <system_error> #ifdef _WIN32 #include <WinError.h> #define LAST_ERROR WSAEADDRINUSE #else #include <errno.h> #define LAST_ERROR EADDRINUSE #endif #define ERRC std::errc::address_in_use #define TRY(...) \ { \ std::error_code ec = {__VA_ARGS__}; \ std::cout << std::boolalpha << (ec == ERRC) << "\t" << ec.value() << "\t" \ << ec.message() << "\n"; \ } int main() { TRY(static_cast<int>(ERRC), std::system_category()) TRY(static_cast<int>(ERRC), std::generic_category()) // note: same as make_error_code TRY(static_cast<int>(LAST_ERROR), std::system_category()) TRY(static_cast<int>(LAST_ERROR), std::generic_category()) // note: same as make_error_code return 0; } 

On Windows:

false 100 Cannot create another system semaphore. true 100 address in use true 10048 Only one usage of each socket address (protocol/network address/port) is normally permitted. false 10048 unknown error 

On POSIX:

true 98 Address already in use true 98 Address already in use true 98 Address already in use true 98 Address already in use 

I get similar results testing with these triplets of equivalent error codes:

equivalent errc Windows POSIX errc::broken_pipe ERROR_BROKEN_PIPE EPIPE errc::filename_too_long ERROR_BUFFER_OVERFLOW ENAMETOOLONG errc::not_supported ERROR_NOT_SUPPORTED ENOTSUP errc::operation_would_block WSAEWOULDBLOCK EWOULDBLOCK 

If anyone's interested, here's a list of std::errcs mapped to == WinError.h constants. This is checking if (std::error_code(static_cast<int>(win_error_constant), std::system_category()) == errc).

address_family_not_supported: WSAEAFNOSUPPORT address_in_use: WSAEADDRINUSE address_not_available: WSAEADDRNOTAVAIL already_connected: WSAEISCONN argument_list_too_long: argument_out_of_domain: bad_address: WSAEFAULT bad_file_descriptor: WSAEBADF bad_message: broken_pipe: ERROR_BROKEN_PIPE connection_aborted: WSAECONNABORTED connection_already_in_progress: WSAEALREADY connection_refused: WSAECONNREFUSED connection_reset: WSAECONNRESET cross_device_link: ERROR_NOT_SAME_DEVICE destination_address_required: WSAEDESTADDRREQ device_or_resource_busy: ERROR_BUSY_DRIVE ERROR_BUSY ERROR_OPEN_FILES ERROR_DEVICE_IN_USE directory_not_empty: ERROR_DIR_NOT_EMPTY executable_format_error: file_exists: ERROR_FILE_EXISTS ERROR_ALREADY_EXISTS file_too_large: filename_too_long: ERROR_BUFFER_OVERFLOW WSAENAMETOOLONG function_not_supported: ERROR_INVALID_FUNCTION host_unreachable: WSAEHOSTUNREACH identifier_removed: illegal_byte_sequence: inappropriate_io_control_operation: interrupted: WSAEINTR invalid_argument: ERROR_INVALID_HANDLE ERROR_INVALID_PARAMETER ERROR_NEGATIVE_SEEK ERROR_DIRECTORY ERROR_REPARSE_TAG_INVALID WSAEINVAL invalid_seek: io_error: ERROR_SEEK ERROR_WRITE_FAULT ERROR_READ_FAULT ERROR_OPEN_FAILED ERROR_CANTOPEN ERROR_CANTREAD ERROR_CANTWRITE is_a_directory: message_size: WSAEMSGSIZE network_down: WSAENETDOWN network_reset: WSAENETRESET network_unreachable: WSAENETUNREACH no_buffer_space: WSAENOBUFS no_child_process: no_link: no_lock_available: ERROR_LOCK_VIOLATION ERROR_LOCKED no_message_available: no_message: no_protocol_option: WSAENOPROTOOPT no_space_on_device: ERROR_HANDLE_DISK_FULL ERROR_DISK_FULL no_stream_resources: no_such_device_or_address: no_such_device: ERROR_INVALID_DRIVE ERROR_BAD_UNIT ERROR_DEV_NOT_EXIST no_such_file_or_directory: ERROR_FILE_NOT_FOUND ERROR_PATH_NOT_FOUND ERROR_BAD_NETPATH ERROR_INVALID_NAME no_such_process: not_a_directory: not_a_socket: WSAENOTSOCK not_a_stream: not_connected: WSAENOTCONN not_enough_memory: ERROR_NOT_ENOUGH_MEMORY ERROR_OUTOFMEMORY not_supported: ERROR_NOT_SUPPORTED operation_canceled: ERROR_OPERATION_ABORTED operation_in_progress: WSAEINPROGRESS operation_not_permitted: operation_not_supported: WSAEOPNOTSUPP operation_would_block: WSAEWOULDBLOCK owner_dead: permission_denied: ERROR_ACCESS_DENIED ERROR_INVALID_ACCESS ERROR_CURRENT_DIRECTORY ERROR_WRITE_PROTECT ERROR_SHARING_VIOLATION ERROR_CANNOT_MAKE ERROR_NOACCESS WSAEACCES protocol_error: protocol_not_supported: WSAEPROTONOSUPPORT read_only_file_system: resource_deadlock_would_occur: resource_unavailable_try_again: ERROR_NOT_READY ERROR_RETRY result_out_of_range: state_not_recoverable: stream_timeout: text_file_busy: timed_out: WSAETIMEDOUT too_many_files_open_in_system: too_many_files_open: ERROR_TOO_MANY_OPEN_FILES WSAEMFILE too_many_links: too_many_symbolic_link_levels: value_too_large: wrong_protocol_type: WSAEPROTOTYPE.0 

Comments