Reference implementation for proposed SG14
<system_error2>) in C++ 11
(C) 2018 Niall Douglas http://www.nedproductions.biz/ Please send feedback to the SG14 study group mailing list at https://groups.google.com/a/isocpp.org/d/forum/sg14.
Docs: https://ned14.github.io/status-code/ (reference API docs are at bottom of page) Linux: Windows:
Solves the problems for low latency/large code base users with
as listed by WG21 P0824. This proposed
library is EXPERIMENTAL and is subject to change as the committee evolves the design.
To fetch a drop-in standalone single file implementation:
- Portable to any C++ 11 compiler. These are known to work:
- >= GCC 5 (due to requiring libstdc++ 5 for sufficient C++ 11 type traits)
- >= clang 3.3 with a new enough libstdc++ (previous clangs don't implement inheriting constructors)
- >= Visual Studio 2015 (previous MSVC's don't implement inheriting constructors)
- Comes with built in POSIX, Win32, NT kernel, Microsoft COM and
std::error_codestatus code domains.
std::erroras proposed by P0709 Zero-overhead deterministic exceptions.
- Aims to cause zero code generated by the compiler most of the time.
- Never calls
- Header-only library friendly.
- Type safe yet with type erasure in public interfaces so it can scale across huge codebases.
- Minimum compile time load, making it suitable for use in the global headers of multi-million line codebases.
Example of use:
Does not cause
#include <string>, and thus including the entire STL allocator and algorithm machinery, thus preventing use in freestanding C++ as well as substantially impacting compile times which can be a showstopper for very large C++ projects. Only includes the following headers:
<atomic>to reference count localised strings retrieved from the OS.
<cassert>to trap when misuse occurs.
<cerrno>for the generic POSIX error codes (
errno) which is required to define
<cstddef>for the definition of
size_tand other types.
<cstring>for the system call to fetch a localised string and C string functions.
<exception>for the basic
std::exceptiontype so we can optionally throw STL exceptions.
<initializer_list>so we can permit in-place construction.
<new>so we can perform placement new.
<type_traits>as we need to do some very limited metaprogramming.
<utility>if on C++ 17 or later for
All of the above headers are on the "fast parse" list at https://github.com/ned14/stl-header-heft.
These may look like a lot, but in fact just including
<atomic>on libstdc++ actually brings in most of the others in any case, and a total of 200Kb (8,000 lines) of text is including by
system_error2.hppon libstdc++ 7. Compiling a file including
status_code.hpptakes less than 150 ms with clang 3.3 as according to the
-ftime-reportdiagnostic (a completely empty file takes 5 ms).
std::error_codewhich was designed before
constexpr, this proposed implementation has all-
constexprconstruction and destruction with as many operations as possible being trivial or literal, with only those exact minimum operations which require runtime code generation being non-trivial (note: requires C++ 14 for a complete implementation of this).
This in turn means that we solve a long standing problem with
std::error_categoryin that it is not possible to define a safe custom C++ 11 error category in a header only library where semantic comparisons would randomly break depending on the direction of wind blowing when the linker ran. This proposed design is 100% safe to use in header only libraries.
std::error_code's boolean conversion operator i.e.
if(ec) ...has become unfortunately ambiguous in real world C++ out there. Its correct meaning is "if
echas a non-zero value". Unfortunately, much code out in the wild uses it as if "if
ecis errored". This is incorrect, though safe most of the time where
ec's category is well known i.e. non-zero values are always an error. For unknown categories supplied by third party code however, it is dangerous and leads to unpleasant, hard-to-debug, surprise.
status_codeproposed here suffers from no such ambiguity. It can be one of exactly three meanings: (i) success (ii) failure (iii) empty (uninitialised). There is no boolean conversion operator, so users must write out exactly what they mean e.g.
status_codecan now represent successful (informational) codes as well as failure codes. Unlike
std::error_codewhere zero is given special meaning, we impose no requirements at all on the choice of coding. This permits safe usage of more complex C status coding such as the NT kernel's
NTSTATUS, which is a
LONGwhereby bits 31 and 30 determine which of four categories the status is (success, informational, warning, error), or the very commone case where negative numbers mean failure and positive numbers mean success-with-information.
The relationship between
std::error_conditionis confusing to many users reading code based on
<system_error>, specifically when is a comparison between codes semantic or literal?
status_codemakes all comparisons semantic, always. If you want a literal comparison, you can do one by hand by comparing domains and values directly.
std::error_codeenforced its value to always be an
int. This is problematic for coding systems which might use a
longand implement coding namespaces within the extended number of bits, or for end users wishing to combine a code with a
void *in order to transmit payload or additional context. As a result,
status_codeis templated to its domain, and the domain sets its type. A type erased edition of
status_code<D>is available as
status_code<void>, this is for obvious reasons non-copyable, non-movable and non-destructible.
A more useful type erased edition is
status_code<erased<T>>which is available if
D::value_typeis trivially copyable,
Tis an integral type, and
sizeof(T) >= sizeof(D::value_type). This lets you use
status_code<erased<T>>in all your public interfaces without restrictions. As a pointer to the original category is retained, and trivially copyable types may be legally copied by
memcpy(), type erased status codes work exactly as normal, except that publicly it does not advertise its type.
std::system_categoryassumes that there is only one "system" error coding, something mostly true on POSIX, but not elsewhere. This library defines
system_codeto a type erased status code sufficiently large enough to carry any of the system error codings on the current platform. This allows code to construct the precise error code for the system failure in question, and return it type erased from the function. Depending on the system call which failed, a function may therefore return any one of many system code domains.
<system_error>code written for POSIX uses
std::generic_categorywhen they really meant
std::system_categorybecause the two are interchangeable on POSIX. Further confusion stems from
std::error_conditionalso sharing the same coding and type. This causes portability problems. This library's
generic_codehas a value type of
errcwhich is a strong enum. This prevents implicit confusion with
posix_code, whose value type is an
errnoreturns. There is no distinction between codes and conditions in this library, rather we treat
generic_codeas something special, because it represents
errc. The cleanup of these ambiguities in
<system_error>should result in users writing clearer code with fewer unintended portability problems.