(C) 2018 - 2021 Niall Douglas http://www.nedproductions.biz/ Please send feedback to the SG14 study group mailing list at https://lists.isocpp.org/mailman/listinfo.cgi/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 <system_error>
as listed by WG21 P0824. This proposed <system_error2>
library is EXPERIMENTAL and is subject to change as the committee evolves the design.
The proposal paper for this library is WG21 P1028.
To fetch a drop-in standalone single file implementation:
wget https://github.com/ned14/status-code/raw/master/single-header/system_error2.hpp
If you'd like a 'ready to go' T-or-E variant return solution where your functions
just return a result<T> which can transport either a success or a failure, consider using
Experimental.Outcome which bundles a
copy of this library inside the standalone Outcome and Boost.Outcome distributions.
You can find an example of use
here.
Experimental.Outcome works great with C++ exceptions globally disabled, and includes
only a very minimal set of C++ headers.
- 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,
getaddrinfo()andstd::error_codestatus code domains. - Implements
std::erroras proposed by P0709 Zero-overhead deterministic exceptions. - Aims to cause zero code generated by the compiler most of the time.
- Never calls
malloc(). - 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.
- Has a 'not POSIX' configuration
SYSTEM_ERROR2_NOT_POSIX, suitable for using this library on non-POSIX non-Windows platforms.
| POSIX | Windows |
|---|---|
|
|
| Portable code | |
| |
Defining a custom status code domain requires writing a lot of tedious boilerplate. End user Jesse Towner suggested a simplified declarative API so arbitrary enumeration types can be wrapped into a custom status code domain with a minimum of effort.
Note that this support requires a minimum of C++ 14 in the compiler. It is not defined if in C++ 11.
// This is some third party enumeration type in another namespace
namespace another_namespace
{
// "Initialiser list" custom status code domain
enum class AnotherCode : size_t
{
success1,
goaway,
success2,
error2
};
} // namespace another_namespace
// To synthesise a custom status code domain for `AnotherCode`, inject the following
// template specialisation:
SYSTEM_ERROR2_NAMESPACE_BEGIN
template <>
struct quick_status_code_from_enum<another_namespace::AnotherCode>
: quick_status_code_from_enum_defaults<another_namespace::AnotherCode>
{
// Text name of the enum
static constexpr const auto domain_name = "Another Code";
// Unique UUID for the enum. PLEASE use https://www.random.org/cgi-bin/randbyte?nbytes=16&format=h
static constexpr const auto domain_uuid = "{be201f65-3962-dd0e-1266-a72e63776a42}";
// Map of each enum value to its text string, and list of semantically equivalent errc's
static const std::initializer_list<mapping≶ &value_mappings()
{
static const std::initializer_list<mapping> v = {
// Format is: { enum value, "string representation", { list of errc mappings ... } }
{another_namespace::AnotherCode::success1, "Success 1", {errc::success}}, //
{another_namespace::AnotherCode::goaway, "Go away", {errc::permission_denied}}, //
{another_namespace::AnotherCode::success2, "Success 2", {errc::success}}, //
{another_namespace::AnotherCode::error2, "Error 2", {errc::function_not_supported}}, //
};
return v;
}
// Completely optional definition of mixin for the status code synthesised from `Enum`.
// It can be omitted.
template <class Base> struct mixin : Base
{
using Base::Base;
// A custom method on the synthesised status code
constexpr int custom_method() const { return 42; }
};
};
SYSTEM_ERROR2_NAMESPACE_END
// If you wish easy manufacture of status codes from AnotherCode:
namespace another_namespace
{
// ADL discovered, must be in same namespace as AnotherCode
constexpr inline
SYSTEM_ERROR2_NAMESPACE::quick_status_code_from_enum_code<another_namespace::AnotherCode>
status_code(AnotherCode c) { return c; }
} // namespace another_namespace
// Make a status code of the synthesised code domain for `AnotherCode`
SYSTEM_ERROR2_CONSTEXPR14 auto v = status_code(another_namespace::AnotherCode::error2);
assert(v.value() == another_namespace::AnotherCode::error2);
assert(v.custom_method() == 42);
// If you don't need custom methods, just use system_code, all erased
// status codes recognise quick_status_code_from_enum<Enum>
SYSTEM_ERROR2_NAMESPACE::system_code v2(another_namespace::AnotherCode::error2);
-
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 defineerrc.<cstddef>for the definition ofsize_tand other types.<cstring>for the system call to fetch a localised string and C string functions.<exception>for the basicstd::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 forstd::in_place.
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 bysystem_error2.hppon libstdc++ 7. Compiling a file includingstatus_code.hpptakes less than 150 ms with clang 3.3 as according to the-ftime-reportdiagnostic (a completely empty file takes 5 ms). -
Unlike
std::error_codewhich was designed beforeconstexpr, 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 "ifechas a non-zero value". Unfortunately, much code out in the wild uses it as if "ifecis errored". This is incorrect, though safe most of the time whereec'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.The
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.if(sc.success()) ...,if(sc.failure()) ...,if(sc.empty()) .... -
Relatedly,
status_codecan now represent successful (informational) codes as well as failure codes. Unlikestd::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'sNTSTATUS, which is aLONGwhereby 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_codeandstd::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 anint. This is problematic for coding systems which might use alongand implement coding namespaces within the extended number of bits, or for end users wishing to combine a code with avoid *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 ofstatus_code<D>is available asstatus_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 ifD::value_typeis trivially copyable,Tis an integral type, andsizeof(T) >= sizeof(D::value_type). This lets you usestatus_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 bymemcpy(), 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 definessystem_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. -
Too much
<system_error>code written for POSIX usesstd::generic_categorywhen they really meantstd::system_categorybecause the two are interchangeable on POSIX. Further confusion stems fromstd::error_conditionalso sharing the same coding and type. This causes portability problems. This library'sgeneric_codehas a value type oferrcwhich is a strong enum. This prevents implicit confusion withposix_code, whose value type is anintsame aserrnoreturns. There is no distinction between codes and conditions in this library, rather we treatgeneric_codeas something special, because it representserrc. The cleanup of these ambiguities in<system_error>should result in users writing clearer code with fewer unintended portability problems.