I. Design
There is a need in C++ for dealing with failures without using exceptions.
This is not so much driven by actual inefficiencies (as opposed to
perceived inefficiencies) of exception handling, but by the fact that not
all C++ code in existence is exception-safe, and the fact that some C++
programmers work in environments where exception handling is disabled, as
ill-advised such a decision may be.
In my opinion the library should drop option<T> and result<T> altogether
and should be reduced to outcome<T> only, which can be empty, or hold a T,
or hold an exception (but not error code, see below.) This is critical for
a library that aims to provide a _common_ general mechanism for dealing
with failures in interfaces; providing a rich set of alternatives works
against that goal.
(It seems desirable to get rid of the empty state too but the discussions
during the review period show that this might cause more problems than it
solves.)
Secondly, it is not a good idea to use error codes or any other value as
means to dispatch on different kinds of failures because semantically such
dispatch should be static: there is some _code_ which detects the error,
and there is some other _code_ that needs to bind it and handle it. This is
one reason why in C++ catch dispatches by the static type of the exception
objects, not by some exception.what() value.
Further, using static types to communicate different kinds of failures
allows users to recognize and handle an entire class of errors by means of
implicit type conversions, by organizing error types in a hierarchy. For
example:
struct io_error: virtual std::runtime_error { };
struct read_error: virtual io_error { };
struct write_error: virtual io_error { };
struct file_error: virtual io_error { };
struct file_read_error: virtual file_error, virtual read_error { };
struct parse_error: virtual std::runtime_error { };
struct syntax_error: virtual parse_error { };
With this hierarchy, we can use read_error to handle any kind of read
errors (not just file-related), file_error to handle any kind of file
failures (read or write), etc.
That said, outcome<T> should also be able to transport values, however
their purpose should not be to tell _what_ went wrong, but to provide
additional information.
It must be stressed that such additional information should be decoupled
from the classification of the error. For example:
outcome