Class template
is a vocabulary type which contains an expected value of type
, or an error
. The class skews towards behaving like a
, because its intended use is when the expected type is contained. When something unexpected occurs, more typing is required. When all is good, code mostly looks as if a
were being handled.
Class template
contains either:
-
A value of type
, the expected value type; orT -
A value of type
, an error type used when an unexpected outcome occured.E
The interface can be queried as to whether the underlying value is the expected value (of type
) or an unexpected value (of type
). The original idea comes from Andrei Alexandrescu C++ and Beyond 2012: Systematic Error Handling in C++ Alexandrescu.Expected, which he revisited in CppCon 2018, including mentions of this paper.
The interface and the rational are based on
[N3793]. We consider
as a supplement to
, expressing why an expected value isn’t contained in the object.
1. Revision History
This paper is an update to [P0323r9], which was wording-ready but targetted the Library Fundamentals TS v3.
In the 2021-04-06 LEWG telecon, LEWG decided to target the IS instead of a TS.
Poll: P0323r9 should add a feature test macro, change the namespace/header
from
to
to be re-targeted to the IS, forward it to
electronic polling to forward to LWG
SF | F | N | A | SA |
6 | 6 | 5 | 1 | 0 |
Related papers:
-
[P0323r3] was the last paper with a rationale. LEWG asked that the rationale be dropped from r4 onwards. The rationale is now back in r10, to follow the new LEWG policy of preserving rationales.
-
Some design issues were discussed by LWG and answered in [P1051r0].
-
[P0650r2] proposes monadic interfaces for
and other types.expected -
[P0343r1] proposes meta-programming high-order functions.
-
[P0262r1] is a related proposal for status / optional value.
-
[P0157R0] describes when to use each of the different error report mechanism.
-
[P0762r0] discussed how this paper interacts with boost.Outcome.
-
[P1095r0] proposes zero overhead deterministic failure.
-
[P0709r4] discussed zero-overhead deterministic exceptions.
-
[P1028R3] proposes
and standardstatus_code
object.error
2. Motivation
C++'s two main error mechanisms are exceptions and return codes. Characteristics of a good error mechanism are:
-
Error visibility: Failure cases should appear throughout the code review: debugging can be painful if errors are hidden.
-
Information on errors: Errors should carry information from their origin, causes and possibly the ways to resolve it.
-
Clean code: Treatment of errors should be in a separate layer of code and as invisible as possible. The reader could notice the presence of exceptional cases without needing to stop reading.
-
Non-Intrusive error: Errors should not monopolize the communication channel dedicated to normal code flow. They must be as discrete as possible. For instance, the return of a function is a channel that should not be exclusively reserved for errors.
The first and the third characteristic seem contradictory and deserve further explanation. The former points out that errors not handled should appear clearly in the code. The latter tells us that error handling must not interfere with the legibility, meaning that it clearly shows the normal execution flow.
Here is a comparison between the exception and return codes:
Exception | Return error code | |
Visibility | Not visible without further analysis of the code. However, if an exception is thrown, we can follow the stack trace. | Visible at the first sight by watching the prototype of the called function. However ignoring return code can lead to undefined results and it can be hard to figure out the problem. |
Informations | Exceptions can be arbitrarily rich. |
Historically a simple integer. Nowadays, the header
|
Clean code | Provides clean code, exceptions can be completely invisible for the caller. | Force you to add, at least, a if statement after each function call. |
Non-Intrusive | Proper communication channel. | Monopolization of the return channel. |
We can do the same analysis for the
class and observe the advantages over the classic error reporting systems.
-
Error visibility: It takes the best of the exception and error code. It’s visible because the return type is
and users cannot ignore the error case if they want to retrieve the contained value.expected < T , E > -
Information: Arbitrarily rich.
-
Clean code: The monadic interface of expected provides a framework delegating the error handling to another layer of code. Note that
can also act as a bridge between an exception-oriented code and a nothrow world.expected < T , E > -
Non-Intrusive: Use the return channel without monopolizing it.
Other notable characteristics of
include:
-
Associates errors with computational goals.
-
Naturally allows multiple errors inflight.
-
Teleportation possible.
-
Across thread boundaries.
-
On weak executors which don’t support thread-local storage.
-
Across no-throw subsystem boundaries.
-
Across time: save now, throw later.
-
Collect, group, combine errors.
-
Much simpler for a compiler to optimize.
2.1. Sample Usecase
The following is how WebKit-based browsers parse URLs and use
to denote failure:
template < typename CharacterType > Expected < uint32_t , URLParser :: IPv4PieceParsingError > URLParser :: parseIPv4Piece ( CodePointIterator < CharacterType >& iterator , bool & didSeeSyntaxViolation ) { enum class State : uint8_t { UnknownBase , Decimal , OctalOrHex , Octal , Hex , }; State state = State :: UnknownBase ; Checked < uint32_t , RecordOverflow > value = 0 ; if ( ! iterator . atEnd () && * iterator == '.' ) return makeUnexpected ( IPv4PieceParsingError :: Failure ); while ( ! iterator . atEnd ()) { if ( isTabOrNewline ( * iterator )) { didSeeSyntaxViolation = true; ++ iterator ; continue ; } if ( * iterator == '.' ) { ASSERT ( ! value . hasOverflowed ()); return value . unsafeGet (); } switch ( state ) { case State :: UnknownBase : if ( UNLIKELY ( * iterator == '0' )) { ++ iterator ; state = State :: OctalOrHex ; break ; } state = State :: Decimal ; break ; case State :: OctalOrHex : didSeeSyntaxViolation = true; if ( * iterator == 'x' || * iterator == 'X' ) { ++ iterator ; state = State :: Hex ; break ; } state = State :: Octal ; break ; case State :: Decimal : if ( ! isASCIIDigit ( * iterator )) return makeUnexpected ( IPv4PieceParsingError :: Failure ); value *= 10 ; value += * iterator - '0' ; if ( UNLIKELY ( value . hasOverflowed ())) return makeUnexpected ( IPv4PieceParsingError :: Overflow ); ++ iterator ; break ; case State :: Octal : ASSERT ( didSeeSyntaxViolation ); if ( * iterator < '0' || * iterator > '7' ) return makeUnexpected ( IPv4PieceParsingError :: Failure ); value *= 8 ; value += * iterator - '0' ; if ( UNLIKELY ( value . hasOverflowed ())) return makeUnexpected ( IPv4PieceParsingError :: Overflow ); ++ iterator ; break ; case State :: Hex : ASSERT ( didSeeSyntaxViolation ); if ( ! isASCIIHexDigit ( * iterator )) return makeUnexpected ( IPv4PieceParsingError :: Failure ); value *= 16 ; value += toASCIIHexValue ( * iterator ); if ( UNLIKELY ( value . hasOverflowed ())) return makeUnexpected ( IPv4PieceParsingError :: Overflow ); ++ iterator ; break ; } } ASSERT ( ! value . hasOverflowed ()); return value . unsafeGet (); }
These results are then accumulated in a vector , and different failure conditions are handled differently. An important fact to internalize is that the first failure encountered isn’t necessarily the one which is returned, which is why exceptions aren’t a good solution here: parsing must continue.
template < typename CharacterTypeForSyntaxViolation , typename CharacterType > Expected < URLParser :: IPv4Address , URLParser :: IPv4ParsingError > URLParser :: parseIPv4Host ( const CodePointIterator < CharacterTypeForSyntaxViolation >& iteratorForSyntaxViolationPosition , CodePointIterator < CharacterType > iterator ) { Vector < Expected < uint32_t , URLParser :: IPv4PieceParsingError > , 4 > items ; bool didSeeSyntaxViolation = false; if ( ! iterator . atEnd () && * iterator == '.' ) return makeUnexpected ( IPv4ParsingError :: NotIPv4 ); while ( ! iterator . atEnd ()) { if ( isTabOrNewline ( * iterator )) { didSeeSyntaxViolation = true; ++ iterator ; continue ; } if ( items . size () >= 4 ) return makeUnexpected ( IPv4ParsingError :: NotIPv4 ); items . append ( parseIPv4Piece ( iterator , didSeeSyntaxViolation )); if ( ! iterator . atEnd () && * iterator == '.' ) { ++ iterator ; if ( iterator . atEnd ()) syntaxViolation ( iteratorForSyntaxViolationPosition ); else if ( * iterator == '.' ) return makeUnexpected ( IPv4ParsingError :: NotIPv4 ); } } if ( ! iterator . atEnd () || ! items . size () || items . size () > 4 ) return makeUnexpected ( IPv4ParsingError :: NotIPv4 ); for ( const auto & item : items ) { if ( ! item . hasValue () && item . error () == IPv4PieceParsingError :: Failure ) return makeUnexpected ( IPv4ParsingError :: NotIPv4 ); } for ( const auto & item : items ) { if ( ! item . hasValue () && item . error () == IPv4PieceParsingError :: Overflow ) return makeUnexpected ( IPv4ParsingError :: Failure ); } if ( items . size () > 1 ) { for ( size_t i = 0 ; i < items . size () - 1 ; i ++ ) { if ( items [ i ]. value () > 255 ) return makeUnexpected ( IPv4ParsingError :: Failure ); } } if ( items [ items . size () - 1 ]. value () >= pow256 ( 5 - items . size ())) return makeUnexpected ( IPv4ParsingError :: Failure ); if ( didSeeSyntaxViolation ) syntaxViolation ( iteratorForSyntaxViolationPosition ); for ( const auto & item : items ) { if ( item . value () > 255 ) syntaxViolation ( iteratorForSyntaxViolationPosition ); } if ( UNLIKELY ( items . size () != 4 )) syntaxViolation ( iteratorForSyntaxViolationPosition ); IPv4Address ipv4 = items . takeLast (). value (); for ( size_t counter = 0 ; counter < items . size (); ++ counter ) ipv4 += items [ counter ]. value () * pow256 ( 3 - counter ); return ipv4 ; }
2.2. Error retrieval and correction
The major advantage of
over
is the ability to transport an error. Programmer do the following when a function call returns an error:
-
Ignore it.
-
Delegate the responsibility of error handling to higher layer.
-
Try to resolve the error.
Because the first behavior might lead to buggy application, we ignore the usecase. The handling is dependent of the underlying error type, we consider the
and the
types.
2.3. Impact on the standard
These changes are entirely based on library extensions and do not require any language features beyond what is available in C++20.
3. Design rationale
The same rationale described in [N3672] for
applies to
and
should behave almost the same as
(though we advise using
in that case). The following
sections presents the rationale in N3672 applied to
.
3.1. Conceptual model of expected < T , E >
models a discriminated union of types
and
.
is viewed as a value of type
or value of
type
, allocated in the same storage, along with observers to
determine which of the two it is.
The interface in this model requires operations such as comparison to
,
comparison to
, assignment and creation from either. It is easy to determine
what the value of the expected object is in this model: the type it stores (
or
) and either the value of
or the value of
.
Additionally, within the affordable limits, we propose the view that
extends the set of the values of
by the values of type
. This is reflected in initialization, assignment, ordering, and equality
comparison with both
and
. In the case of
,
cannot be a
. As the types
and
could be the same in
,
there is need to tag the values of
to avoid ambiguous expressions. The
deduction guide is proposed for this purpose. However
cannot
be
for a given
.
expected < int , string > ei = 0 ; expected < int , string > ej = 1 ; expected < int , string > ek = unexpected ( string ()); ei = 1 ; ej = unexpected ( E ());; ek = 0 ; ei = unexpected ( E ());; ej = 0 ; ek = 1 ;
3.2. Default E
template paremeter type
At the Toronto meeting LEWG decided against having a default
template
parameter type (
or other). This prevents us from providing an
deduction guides for error construction which is a good thing: an
error was not expected, a
was expected, it’s therefore sensible to force
spelling out unexpected outcomes when generating them.
3.3. Initialization of expected < T , E >
In cases where
and
have value semantic types capable of storing
and
distinct values respectively,
can be seen as an extended
capable of storing
values: these
and
stores. Any valid
initialization scheme must provide a way to put an expected object to any of
these states. In addition, some
s aren’t
and their
expected variants still should be constructible with any set of arguments that
work for
.
As in [N3672], the model retained is to initialize either by providing an
already constructed
or a tagged
. The default constructor required
to
be default-constructible (since
should behave like
as much as
possible).
string s "STR" ; expected < string , errc > es { s }; // requires Copyable<T> expected < string , errc > et = s ; // requires Copyable<T> expected < string , errc > ev = string "STR" ; // requires Movable<T> expected < string , errc > ew ; // expected value expected < string , errc > ex {}; // expected value expected < string , errc > ey = {}; // expected value expected < string , errc > ez = expected < string , errc > {}; // expected value
In order to create an unexpected object, the deduction guide
needs
to be used:
expected < string , int > ep { unexpected ( - 1 )}; // unexpected value, requires Movable<E> expected < string , int > eq = unexpected ( - 1 ); // unexpected value, requires Movable<E>
As in [N3672], and in order to avoid calling move/copy constructor of
, we
use a “tagged” placement constructor:
expected < MoveOnly , errc > eg ; // expected value expected < MoveOnly , errc > eh {}; // expected value expected < MoveOnly , errc > ei { in_place }; // calls MoveOnly{} in place expected < MoveOnly , errc > ej { in_place , "arg" }; // calls MoveOnly{"arg"} in place
To avoid calling move/copy constructor of
, we use a “tagged” placement
constructor:
expected < int , string > ei { unexpect }; // unexpected value, calls string{} in place expected < int , string > ej { unexpect , "arg" }; // unexpected value, calls string{"arg"} in place
An alternative name for
that is coherent with
could be
. Being compatible with
seems more important. So this
proposal doesn’t propose such an
tag.
The alternative and also comprehensive initialization approach, which is
compatible with the default construction of
as
, could
have been a variadic perfect forwarding constructor that just forwards any set
of arguments to the constructor of the contained object of type
.
3.4. Never-empty guarantee
As for
,
ensures that it is
never empty. All instances
of type
guarantee
has
constructed content of one of the types
or
, even if an operation on
has previously failed.
This implies that
may be viewed precisely as a union of exactly its
bounded types. This "never-empty" property insulates the user from the
possibility of undefined
content or an
as
and the significant additional
complexity-of-use attendant with such a possibility.
In order to ensure this property the types
and
must satisfy the
requirements as described in [P0110R0]. Given the nature of the parameter
,
that is, to transport an error, it is expected to be
,
,
and
.
Note however that these constraints are applied only to the operations that need them.
If
is false
, the
function is not defined. In this case, it is the
responsibility of the user to create a temporary and move or copy it.
3.5. The default constructor
Similar data structure includes
,
and
. We can compare how they are default constructed.
-
default constructs to an optional with no value.std :: optional < T > -
default constructs tostd :: variant < T1 ,..., Tn >
if default constructible or it is ill-formedT1 -
default constructs to an invalid future with no shared state associated, that is, no value and no exception.std :: future < T > -
default constructor is equivalent tostd :: optional < T >
.boost :: variant < nullopt_t , T >
This raises several questions about
:
-
Should the default constructor of
behave likeexpected < T , E >
or asvariant < T , unexpected < E >>
?variant < unexpected < E > , T > -
Should the default constructor of
behave likeexpected < T , nullopt_t >
? If yes, how should behave the default constructor ofoptional < T >
? As if initialized withexpected < T , E >
? This would be equivalent to the initialization ofunexpected ( E ())
.variant < unexpected < E > , T > -
Should
provide a default constructor at all? [N3527] presents valid arguments against this approach, e.g.expected < T , E >
would not be possible.array < expected < T , E >>
Requiring
to be default constructible seems less constraining than requiring
to be default constructible (e.g. consider the
example in [N3527]). With the same semantics
would be
with a
meaningful not-a-date state created by default.
The authors consider the arguments in [N3527] valid for
and
, however the committee requested that
default
constructor should behave as constructed with
if
is default
constructible.
3.6. Could Error
be void
isn’t a sensible
template parameter type: the
vocabularity type means "I expect a
, but I may have nothing for you". This
is literally what
is for. If the error is a unit type the user can
use
or
.
3.7. Conversion from T
An object of type
is implicitly convertible to an expected object of type
:
expected < int , errc > ei = 1 ; // works
This convenience feature is not strictly necessary because you can achieve the same effect by using tagged forwarding constructor:
expected < int , errc > ei { in_place , 1 };
It has been demonstrated that this implicit conversion is dangerous [a-gotcha-with-optional].
An alternative will be to make it explicit and add a
(similar to
explicitly convertible from
and implicitly convertible to
.
expected < int , errc > ei = success ( 1 ); expected < int , errc > ej = unexpected ( ec );
The authors consider that it is safer to have the explicit conversion, the
implicit conversion is so friendly that we don’t propose yet an explicit
conversion. In addition
has already be delivered in C++17 and it
has this gotcha.
Further, having
makes code much more verbose than the current implicit
conversion. Forcing the usage of
would make
a much less
useful vocabulary type: if success is expected then success need not be called
out.
3.8. Conversion from E
An object of type
is not convertible to an unexpected object of type
since
and
can be of the same type. The proposed
interface uses a special tag
and
deduction guide to
indicate an unexpected state for
. It is used for construction
and assignment. This might raise a couple of objections. First, this duplication
is not strictly necessary because you can achieve the same effect by using the
tag forwarding constructor:
expected < string , errc > exp1 = unexpected ( 1 ); expected < string , errc > exp2 = { unexpect , 1 }; exp1 = unexpected ( 1 ); exp2 = { unexpect , 1 };
or simply using deduced template parameter for constructors
expected < string , errc > exp1 = unexpected ( 1 ); exp1 = unexpected ( 1 );
While some situations would work with the
syntax, using
makes the programmer’s intention as clear and less cryptic. Compare
these:
expected < vector < int > , errc > get1 () {} return { unexpect , 1 }; } expected < vector < int > , errc > get2 () { return unexpected ( 1 ); } expected < vector < int > , errc > get3 () { return expected < vector < int > , int > { unexpect , 1 }; } expected < vector < int > , errc > get2 () { return unexpected ( 1 ); }
The usage of
is also a consequence of the adapted model for
: a discriminated union of
and
.
3.9. Should we support the exp2 = {}
?
Note also that the definition of
has an explicitly deleted default
constructor. This was in order to enable the reset idiom
which would
otherwise not work due to the ambiguity when deducing the right-hand side
argument.
Now that
defaults to
the meaning of
is to
assign
.
3.10. Observers
In order to be as efficient as possible, this proposal includes observers with
narrow and wide contracts. Thus, the
function has a wide contract. If
the expected object does not contain a value, an exception is thrown. However,
when the user knows that the expected object is valid, the use of
would be more appropriated.
3.11. Explicit conversion to bool
The rational described in [N3672] for
applies to
. The following example therefore combines initialization and value-checking
in a boolean context.
if ( expected < char , errc > ch = readNextChar ()) { // ... }
3.12. has_value ()
following P0032
has been added to follow [P0032R2].
3.13. Accessing the contained value
Even if
has not been used in practice for enough time as
or Boost.Optional, we consider that following the same interface
as
makes the C++ standard library more homogeneous.
The rational described in [N3672] for
applies to
.
3.14. Dereference operator
The indirection operator was chosen because, along with explicit conversion to
, it is a very common pattern for accessing a value that might not be
there:
if ( p ) use ( * p );
This pattern is used for all sort of pointers (smart or raw) and
; it
clearly indicates the fact that the value may be missing and that we return a
reference rather than a value. The indirection operator created some objections
because it may incorrectly imply
and
are a (possibly
smart) pointer, and thus provides shallow copy and comparison semantics. All
library components so far use indirection operator to return an object that is
not part of the pointer’s/iterator’s value. In contrast,
as well as
indirects to the part of its own state. We do not consider it a
problem in the design; it is more like an unprecedented usage of indirection
operator. We believe that the cost of potential confusion is overweighted by the
benefit of an intuitive interface for accessing the contained value.
We do not think that providing an implicit conversion to
would be a good
choice. First, it would require different way of checking for the empty state;
and second, such implicit conversion is not perfect and still requires other
means of accessing the contained value if we want to call a member function on
it.
Using the indirection operator for an object that does not contain a value is undefined behavior. This behavior offers maximum runtime performance.
3.15. Function value
In addition to the indirection operator, we propose the member function
as in [N3672] that returns a reference to the contained value if one exists or
throw an exception otherwise.
void interact () { string s ; cout << "enter number: " ; cin >> s ; expected < int , error > ei = str2int ( s ); try { process_int ( ei . value ()); } catch ( bad_expected_access < error > ) { cout << "this was not a number." ; } }
The exception thrown is
(derived from
) which will contain the stored error.
and
could inherit both from a
exception derived from
, but this is not proposed yet.
3.16. Should expected < T , E >:: value ()
throw E
instead of bad_expected_access < E >
?
As any type can be thrown as an exception, should
throw
instead of
?
Some argument that standard function should throw exceptions that inherit from
, but here the exception throw is given by the user via the type
, it is not the standard library that throws explicitly an exception that
don’t inherit from
.
This could be convenient as the user will have directly the
exception. However it will be more difficult to identify that this was due to a
bad expected access.
If yes, should
throw
instead of
to be coherent?
We don’t propose this.
Other have suggested to throw
if
is
, rethrow if
is
,
if it inherits from
and
otherwise.
An alternative would be to add some customization point that state which exception is thrown but we don’t propose it in this proposal. See the Appendix I.
3.17. Accessing the contained error
Usually, accessing the contained error is done once we know the expected object
has no value. This is why the
function has a narrow contract: it works
only if
does not contain a value.
expected < int , errc > getIntOrZero ( istream_range & r ) { auto r = getInt (); // won’t throw if ( ! r && r . error () == errc :: empty_stream ) { return 0 ; } return r ; }
This behavior could not be obtained with the
method since we want
to return
only if the error is equal to
.
We could as well provide an error access function with a wide contract. We just need to see how to name each one.
3.18. Conversion to the unexpected value
The
function is used to propagate errors, as for example in the next
example:
expected < pair < int , int > , errc > getIntRange ( istream_range & r ) { auto f = getInt ( r ); if ( ! f ) return unexpected ( f . error ()); auto m = matchedString ( ".." , r ); if ( ! m ) return unexpected ( m . error ()); auto l = getInt ( r ); if ( ! l ) return unexpected ( l . error ()); return std :: make_pair ( * f , * l ); }
3.19. Function value_or
The function member
has the same semantics than
[N3672] since the type of
doesn’t matter; hence we can consider that
and the
semantics yields.
This function is a convenience function that should be a non-member function for
and
, however as it is already part of the
interface we propose to have it also for
.
3.20. Equality operators
As for
and
, one of the design goals of
is that
objects of type
should be valid elements in STL containers and
usable with STL algorithms (at least if objects of type
and
are). Equality comparison is essential for
to model concept
. C++ does not have concepts yet, but being regular is still essential
for the type to be effectively used with STL.
3.21. Comparison operators
Comparison operators between
objects, and between mixed
and
, aren’t required at this time. A future proposal could re-adopt
the comparisons as defined in [P0323R2].
3.22. Modifiers
3.23. Resetting the value
Reseting the value of
is similar to
but instead
of building a disengaged
, we build an erroneous
. Hence, the semantics and rationale is the same than in [N3672].
3.24. Tag in_place
This proposal makes use of the "in-place" tag as defined in [C++17]. This
proposal provides the same kind of "in-place" constructor that forwards
(perfectly) the arguments provided to
's constructor into the
constructor of
.
In order to trigger this constructor one has to use the tag
. We need
the extra tag to disambiguate certain situations, like calling
's
default constructor and requesting
's default construction:
expected < Big , error > eb { in_place , "1" }; // calls Big{"1"} in place (no moving) expected < Big , error > ec { in_place }; // calls Big{} in place (no moving) expected < Big , error > ed {}; // calls Big{} (expected state)
3.25. Tag unexpect
This proposal provides an "unexpect" constructor that forwards (perfectly) the
arguments provided to
's constructor into the constructor of
. In
order to trigger this constructor one has to use the tag
.
We need the extra tag to disambiguate certain situations, notably if
and
are the same type.
expected < Big , error > eb { unexpect , "1" }; // calls error{"1"} in place (no moving) expected < Big , error > ec { unexpect }; // calls error{} in place (no moving)
In order to make the tag uniform an additional "expect" constructor could be provided but this proposal doesn’t propose it.
3.26. Requirements on T
and E
Class template
imposes little requirements on
and
: they have
to be complete object type satisfying the requirements of
. Each
operations on
have different requirements and may be disable if
or
doesn’t respect these requirements. For example,
's
move constructor requires that
and
are
,
's copy constructor requires that
and
are
, and so
on. This is because
is a wrapper for
or
: it should
resemble
or
as much as possible. If
and
are
then (and only then) we expect
to be
.
However in order to ensure the never empty guaranties,
requires
to be no throw move constructible. This is normal as the
stands for an
error, and throwing while reporting an error is a very bad thing.
3.27. Expected references
This proposal doesn’t include
references as
C++17
doesn’t include references either.
We need a future proposal.
3.28. Expected void
While it could seem weird to instantiate
with
, it has more
sense for
as it conveys in addition, as
, an error
state. The type
means "nothing is expected, but an error
could occur".
3.29. Making expected a literal type
In [N3672], they propose to make
a literal type, the same reasoning
can be applied to expected. Under some conditions, such that
and
are
trivially destructible, and the same described for
, we propose that
be a literal type.
3.30. Moved from state
We follow the approach taken in
[N3672]. Moving
does
not modify the state of the source (valued or erroneous) of
and the
move semantics is up to
or
.
3.31. I/O operations
For the same reasons as
[N3672] we do not add
and
I/O operations.
3.32. What happens when E
is a status?
When
is a status, as most of the error codes are, and has more than one
value that mean success, setting an
with a successful
value
could be misleading if the user expect in this case to have also a
. In this
case the user should use the proposed
class. However, if
there is only one value
that mean success, there is no such need and
compose better with the monadic interface [P0650R0].
3.33. Do we need an expected < T , E >:: error_or
function?
See [P0786R0].
Do we need to add such an
function? as member?
This function should work for all the ValueOrError types and so could belong to a future ValueOrError proposal.
Not in this proposal.
3.34. Do we need a expected < T , E >:: check_error
function?
See [P0786R0].
Do we want to add such a
function? as member?
This function should work for all the ValueOrError types and so could belong to a future ValueOrError proposal.
Not in this proposal.
3.35. Do we need a expected < T , G >:: adapt_error ( function < E ( G ))
function?
We have the constructor
that allows to transport
explicitly the contained error as soon as it is convertible.
However sometimes we cannot change either of the error types and we could need to do this transformation. This function help to achieve this goal. The parameter is the function doing the error transformation.
This function can be defined on top of the existing interface.
template < class T , class E > expected < T , G > adapt_error ( expected < T , E > const & e , function < G ( E ) > adaptor ) { if ( ! e ) return adaptor ( e . error ()); else return expected < T , G > ( * e ); }
Do we want to add such a
function? as member?
This function should work for all the ValueOrError types and so could belong to a future ValueOrError proposal.
Not in this proposal.
4. Wording
Below, substitute the �
character with a number or name the editor finds
appropriate for the sub-section.
4.1. �.� Expected objects [expected]
4.2. �.�.1 In general [expected.general]
This subclause describes class template
that represents expected
objects. An
object holds an object of type
or an object of
type
and manages the lifetime of the contained objects.
5. �.�.2 Header < expected >
synopsis [expected.synop]
namespace std { // �.�.4, class template expected template < class T , class E > class expected ; // �.�.5, class template unexpected template < class E > class unexpected ; template < class E > unexpected ( E ) -> unexpected < E > ; // �.�.6, class bad_expected_access template < class E > class bad_expected_access ; // �.�.7, Specialization for void template <> class bad_expected_access < void > ; // �.�.8, unexpect tag struct unexpect_t { explicit unexpect_t () = default ; }; inline constexpr unexpect_t unexpect {}; }
5.1. �.�.3 Definitions [expected.defs]
5.2. �.�.4 Class template expected [expected.expected]
template < class T , class E > class expected { public : using value_type = T ; using error_type = E ; using unexpected_type = unexpected < E > ; template < class U > using rebind = expected < U , error_type > ; // �.�.4.1, constructors constexpr expected (); constexpr expected ( const expected & ); constexpr expected ( expected && ) noexcept ( see below ); template < class U , class G > explicit ( see below ) constexpr expected ( const expected < U , G >& ); template < class U , class G > explicit ( see below ) constexpr expected ( expected < U , G >&& ); template < class U = T > explicit ( see below ) constexpr expected ( U && v ); template < class G = E > constexpr expected ( const unexpected < G >& ); template < class G = E > constexpr expected ( unexpected < G >&& ); template < class ... Args > constexpr explicit expected ( in_place_t , Args && ...); template < class U , class ... Args > constexpr explicit expected ( in_place_t , initializer_list < U > , Args && ...); template < class ... Args > constexpr explicit expected ( unexpect_t , Args && ...); template < class U , class ... Args > constexpr explicit expected ( unexpect_t , initializer_list < U > , Args && ...); // �.�.4.2, destructor ~ expected (); // �.�.4.3, assignment expected & operator = ( const expected & ); expected & operator = ( expected && ) noexcept ( see below ); template < class U = T > expected & operator = ( U && ); template < class G = E > expected & operator = ( const unexpected < G >& ); template < class G = E > expected & operator = ( unexpected < G >&& ); // �.�.4.4, modifiers template < class ... Args > T & emplace ( Args && ...); template < class U , class ... Args > T & emplace ( initializer_list < U > , Args && ...); // �.�.4.5, swap void swap ( expected & ) noexcept ( see below ); // �.�.4.6, observers constexpr const T * operator -> () const ; constexpr T * operator -> (); constexpr const T & operator * () const & ; constexpr T & operator * () & ; constexpr const T && operator * () const && ; constexpr T && operator * () && ; constexpr explicit operator bool () const noexcept ; constexpr bool has_value () const noexcept ; constexpr const T & value () const & ; constexpr T & value () & ; constexpr const T && value () const && ; constexpr T && value () && ; constexpr const E & error () const & ; constexpr E & error () & ; constexpr const E && error () const && ; constexpr E && error () && ; template < class U > constexpr T value_or ( U && ) const & ; template < class U > constexpr T value_or ( U && ) && ; // �.�.4.7, Expected equality operators template < class T1 , class E1 , class T2 , class E2 > friend constexpr bool operator == ( const expected < T1 , E1 >& x , const expected < T2 , E2 >& y ); template < class T1 , class E1 , class T2 , class E2 > friend constexpr bool operator != ( const expected < T1 , E1 >& x , const expected < T2 , E2 >& y ); // �.�.4.8, Comparison with T template < class T1 , class E1 , class T2 > friend constexpr bool operator == ( const expected < T1 , E1 >& , const T2 & ); template < class T1 , class E1 , class T2 > friend constexpr bool operator == ( const T2 & , const expected < T1 , E1 >& ); template < class T1 , class E1 , class T2 > friend constexpr bool operator != ( const expected < T1 , E1 >& , const T2 & ); template < class T1 , class E1 , class T2 > friend constexpr bool operator != ( const T2 & , const expected < T1 , E1 >& ); // �.�.4.9, Comparison with unexpected<E> template < class T1 , class E1 , class E2 > friend constexpr bool operator == ( const expected < T1 , E1 >& , const unexpected < E2 >& ); template < class T1 , class E1 , class E2 > friend constexpr bool operator == ( const unexpected < E2 >& , const expected < T1 , E1 >& ); template < class T1 , class E1 , class E2 > friend constexpr bool operator != ( const expected < T1 , E1 >& , const unexpected < E2 >& ); template < class T1 , class E1 , class E2 > friend constexpr bool operator != ( const unexpected < E2 >& , const expected < T1 , E1 >& ); // �.�.4.10, Specialized algorithms template < class T1 , class E1 > friend void swap ( expected < T1 , E1 >& , expected < T1 , E1 >& ) noexcept ( see below ); private : bool has_val ; // exposition only union { value_type val ; // exposition only unexpected_type unex ; // exposition only }; };
Any object of
either contains a value of type
or a value of
type
within its own storage. Implementations are not permitted
to use additional storage, such as dynamic memory, to allocate the object of
type
or the object of type
. These objects are allocated
in a region of the
storage suitably aligned for the types
and
. Members
,
and
are provided for
exposition only.
indicates whether the
object
contains an object of type
.
A program that instantiates the definition of template
for a
reference type, a function type, or for possibly cv-qualified types
,
or
for the
parameter is
ill-formed. A program that instantiates the definition of template
with the
parameter that is not a valid template parameter
for
is ill-formed.
When
is not cv
, it shall meet the requirements of
(Table 27).
shall meet the requirements of
(Table 27).
5.3. �.�.4.1 Constructors [expected.object.ctor]
constexpr expected ();
Constraints:
is true
or
is cv
.
Effects: Value-initializes
if
is not cv
.
Ensures:
is true
.
Throws: Any exception thrown by the selected constructor of
.
Remarks: If value-initialization of
is a constant subexpression or
is cv
this constructor is constexpr.
constexpr expected ( const expected & rhs );
Effects: If
and
is not cv
, initializes
as if
direct-non-list-initializing an object of type
with the expression
.
Otherwise, initializes
as if direct-non-list-initializing an object
of type
with the expression
.
Ensures:
.
Throws: Any exception thrown by the selected constructor of
or
.
Remarks: This constructor is defined as deleted unless:
-
isis_copy_constructible_v < T > true
or
is cvT
; andvoid -
isis_copy_constructible_v < E > true
.
This constructor is a constexpr constructor if:
-
isis_trivially_copy_constructible_v < T > true
or
is cvT
; andvoid -
isis_trivially_copy_constructible_v < E > true
.
constexpr expected ( expected && rhs ) noexcept ( see below );
Constraints:
-
isis_move_constructible_v < T > true
; and -
isis_move_constructible_v < E > true
.
Effects: If
and
is not cv
, initializes
as if
direct-non-list-initializing an object of type
with the expression
.
Otherwise, initializes
as if direct-non-list-initializing an object of
type
with the expression
.
Ensures:
is unchanged,
.
Throws: Any exception thrown by the selected constructor of
or
.
Remarks: The expression inside
is equivalent to:
-
isis_nothrow_move_constructible_v < T > true
or
is cvT
; andvoid -
isis_nothrow_move_constructible_v < E > true
.
Remarks: This constructor is a constexpr constructor if:
-
isis_trivially_move_constructible_v < T > true
or
is cvT
; andvoid -
isis_trivially_move_constructible_v < E > true
.
template < class U , class G > explicit ( see below ) constexpr expected ( const expected < U , G >& rhs );
Constraints:
and
are cv
or:
-
isis_constructible_v < T , const U &> true
; and -
isis_constructible_v < T , expected < U , G >&> false
; and -
isis_constructible_v < T , expected < U , G >&&> false
; and -
isis_constructible_v < T , const expected < U , G >&> false
; and -
isis_constructible_v < T , const expected < U , G >&&> false
; and -
isis_convertible_v < expected < U , G >& , T > false
; and -
isis_convertible_v < expected < U , G >&& , T > false
; and -
isis_convertible_v < const expected < U , G >& , T > false
; and -
isis_convertible_v < const expected < U , G >&& , T > false
; and -
isis_constructible_v < E , const G &> true
; and -
isis_constructible_v < unexpected < E > , expected < U , G >&> false
; and -
isis_constructible_v < unexpected < E > , expected < U , G >&&> false
; and -
isis_constructible_v < unexpected < E > , const expected < U , G >&> false
; and -
isis_constructible_v < unexpected < E > , const expected < U , G >&&> false
; and -
isis_convertible_v < expected < U , G >& , unexpected < E >> false
; and -
isis_convertible_v < expected < U , G >&& , unexpected < E >> false
; and -
isis_convertible_v < const expected < U , G >& , unexpected < E >> false
; and -
isis_convertible_v < const expected < U , G >&& , unexpected < E >> false
.
Effects: If
and
is not cv
, initializes
as if
direct-non-list-initializing an object of type
with the expression
.
Otherwise, initializes
as if direct-non-list-initializing an object
of type
with the expression
.
Ensures:
.
Throws: Any exception thrown by the selected constructor of
or
.
Remarks:
The expression inside
is true
if and only if:
-
andT
are not cvU
andvoid
isis_convertible_v < const U & , T > false
or -
isis_convertible_v < const G & , E > false
.
template < class U , class G > explicit ( see below ) constexpr expected ( expected < U , G >&& rhs );
Constraints:
and
are cv
or:
-
isis_constructible_v < T , U &&> true
; and -
isis_constructible_v < T , expected < U , G >&> false
; and -
isis_constructible_v < T , expected < U , G >&&> false
; and -
isis_constructible_v < T , const expected < U , G >&> false
; and -
isis_constructible_v < T , const expected < U , G >&&> false
; and -
isis_convertible_v < expected < U , G >& , T > false
; and -
isis_convertible_v < expected < U , G >&& , T > false
; and -
isis_convertible_v < const expected < U , G >& , T > false
; and -
isis_convertible_v < const expected < U , G >&& , T > false
; and -
isis_constructible_v < E , G &&> true
; and -
isis_constructible_v < unexpected < E > , expected < U , G >&> false
; and -
isis_constructible_v < unexpected < E > , expected < U , G >&&> false
; and -
isis_constructible_v < unexpected < E > , const expected < U , G >&> false
; and -
isis_constructible_v < unexpected < E > , const expected < U , G >&&> false
; and -
isis_convertible_v < expected < U , G >& , unexpected < E >> false
; and -
isis_convertible_v < expected < U , G >&& , unexpected < E >> false
; and -
isis_convertible_v < const expected < U , G >& , unexpected < E >> false
; and -
isis_convertible_v < const expected < U , G >&& , unexpected < E >> false
.
Effects: If
initializes
as if
direct-non-list-initializing an object of type
with the expression
or nothing if
is cv
.
Otherwise, initializes
as if direct-non-list-initializing an object
of type
with the expression
.
Ensures:
is unchanged,
.
Throws: Any exception thrown by operations specified in the effect clause.
Remarks: The expression inside
is true
if and only if
-
andT
are not cvU
andvoid
isis_convertible_v < U && , T > false
or -
isis_convertible_v < G && , E > false
.
template < class U = T > explicit ( see below ) constexpr expected ( U && v );
Constraints:
-
is not cvT
; andvoid -
isis_constructible_v < T , U &&> true
; and -
isis_same_v < remove_cvref_t < U > , in_place_t > false
; and -
isis_same_v < expected < T , E > , remove_cvref_t < U >> false
; and -
isis_same_v < unexpected < E > , remove_cvref_t < U >> false
.
Effects: Initializes
as if direct-non-list-initializing an
object of type
with the expression
.
Ensures:
is true
.
Throws: Any exception thrown by the selected constructor of
.
Remarks: If
's selected constructor is a constant subexpression, this
constructor is a constexpr constructor.
Remarks: The expression inside
is equivalent to
.
template < class G = E > explicit ( see below ) constexpr expected ( const unexpected < G >& e );
Constraints:
is true
.
Effects: Initializes
as if direct-non-list-initializing
an object of type
with the expression
.
Ensures:
is false
.
Throws: Any exception thrown by the selected constructor of
.
Remarks: If
's selected constructor is a constant
subexpression, this constructor shall be a constexpr constructor.
The expression inside
is equivalent to
.
template < class G = E > explicit ( see below ) constexpr expected ( unexpected < G >&& e );
Constraints:
is true
.
Effects: Initializes
as if direct-non-list-initializing
an object of type
with the expression
.
Ensures:
is false
.
Throws: Any exception thrown by the selected constructor of
.
Remarks: If
's selected constructor is a constant
subexpression, this constructor is a constexpr constructor. The expression
inside
is equivalent to:
is true
.
The expression inside
is equivalent to
.
template < class ... Args > constexpr explicit expected ( in_place_t , Args && ... args );
Constraints:
-
is cvT
andvoid
; orsizeof ...( Args ) == 0 -
is not cvT
andvoid
isis_constructible_v < T , Args ... > true
.
Effects: If
is cv
, no effects. Otherwise, initializes
as if
direct-non-list-initializing an object of type
with the arguments
.
Ensures:
is true
.
Throws: Any exception thrown by the selected constructor of
.
Remarks: If
's constructor selected for the initialization is a constant subexpression,
this constructor is a constexpr constructor.
template < class U , class ... Args > constexpr explicit expected ( in_place_t , initializer_list < U > il , Args && ... args );
Constraints:
is not cv
and
is true
.
Effects: Initializes
as if direct-non-list-initializing an
object of type
with the arguments
.
Ensures:
is true
.
Throws: Any exception thrown by the selected constructor of
.
Remarks: If
's constructor selected for the initialization is a constant subexpression,
this constructor is a constexpr constructor.
template < class ... Args > constexpr explicit expected ( unexpect_t , Args && ... args );
Constraints:
is true
.
Effects: Initializes
as if direct-non-list-initializing
an object of type
with the arguments
.
Ensures:
is false
.
Throws: Any exception thrown by the selected constructor of
.
Remarks: If
's constructor selected for the initialization is a
constant subexpression, this constructor is a constexpr constructor.
template < class U , class ... Args > constexpr explicit expected ( unexpect_t , initializer_list < U > il , Args && ... args );
Constraints:
is true
.
Effects: Initializes
as if direct-non-list-initializing
an object of type
with the arguments
.
Ensures:
is false
.
Throws: Any exception thrown by the selected constructor of
.
Remarks: If
's constructor selected for the initialization is a
constant subexpression, this constructor is a constexpr constructor.
5.4. �.�.4.2 Destructor [expected.object.dtor]
~ expected ();
Effects: If
is not cv
and
is false
and
, calls
. If
is false
and
, calls
.
Remarks: If either
is cv
or
is true
, and
is true
, then this destructor
is a trivial destructor.
5.5. �.�.4.3 Assignment [expected.object.assign]
expected & operator = ( const expected & rhs ) noexcept ( see below );
Effects: See Table editor-please-pick-a-number-0
|
| |
| assigns to if is not cv
|
|
|
| assigns to
|
Returns:
.
Ensures:
.
Throws: Any exception thrown by the operations specified in the effect clause.
Remarks: If any exception is thrown,
and
remain unchanged.
If an exception is thrown during the call to
's or
's copy
constructor, no effect. If an exception is thrown during the call to
's or
's copy assignment, the state of its contained value is as defined
by the exception safety guarantee of
's or
's copy assignment.
This operator is defined as deleted unless:
-
is cvT
andvoid
isis_copy_assignable_v < E > true
and
isis_copy_constructible_v < E > true
; or -
is not cvT
andvoid
isis_copy_assignable_v < T > true
and
isis_copy_constructible_v < T > true
and
isis_copy_assignable_v < E > true
and
isis_copy_constructible_v < E > true
and (
isis_nothrow_move_constructible_v < E > true
or
isis_nothrow_move_constructible_v < T > true
).
expected & operator = ( expected && rhs ) noexcept ( see below );
Effects: See Table editor-please-pick-a-number-1
|
| |
| move assign to if is not cv
|
|
|
| move assign to
|
Returns:
.
Ensures:
.
Remarks: The expression inside noexcept is equivalent to:
is true
and
is true
.
If any exception is thrown,
and
remain
unchanged. If an exception is thrown during the call to
's copy constructor,
no effect. If an exception is thrown during the call to
's copy assignment,
the state of its contained value is as defined by the exception safety guarantee
of
's copy assignment. If an exception is thrown during the call to
's
copy assignment, the state of its contained
is as defined by
the exception safety guarantee of
's copy assignment.
This operator is defined as deleted unless:
-
is cvT
andvoid
isis_nothrow_move_constructible_v < E > true
and
isis_nothrow_move_assignable_v < E > true
; or -
is not cvT
andvoid
isis_move_constructible_v < T > true
and
isis_move_assignable_v < T > true
and
isis_nothrow_move_constructible_v < E > true
and
isis_nothrow_move_assignable_v < E > true
.
template < class U = T > expected < T , E >& operator = ( U && v );
Constraints:
-
isis_void_v < T > false
; and -
isis_same_v < expected < T , E > , remove_cvref_t < U >> false
; and -
isconjunction_v < is_scalar < T > , is_same < T , decay_t < U >>> false
; and -
isis_constructible_v < T , U > true
; and -
isis_assignable_v < T & , U > true
; and -
isis_nothrow_move_constructible_v < E > true
.
Effects: See Table editor-please-pick-a-number-2
|
|
If , assigns to
|
if
otherwise
|
Returns:
.
Ensures:
is true
.
Remarks: If any exception is thrown,
remains
unchanged. If an exception is thrown during the call to
's constructor, no
effect. If an exception is thrown during the call to
's copy assignment, the
state of its contained value is as defined by the exception safety guarantee of
's copy assignment.
template < class G = E > expected < T , E >& operator = ( const unexpected < G >& e );
Constraints:
is true
and
is true
.
Effects: See Table editor-please-pick-a-number-3
|
|
assigns to
|
|
Returns:
.
Ensures:
is false
.
Remarks: If any exception is thrown,
remains unchanged.
expected < T , E >& operator = ( unexpected < G >&& e );
Effects: See Table editor-please-pick-a-number-4
|
|
| move assign to
|
Constraints:
is true
and
is true
.
Returns:
.
Ensures:
is false
.
Remarks: If any exception is thrown,
remains unchanged.
void expected < void , E >:: emplace ();
Effects:
If
-
destroys
by callingunex
,unexpect . ~ unexpected < E > () -
set
tohas_val true
Ensures:
is true
.
Throws: Nothing
template < class ... Args > T & emplace ( Args && ... args );
Constraints:
is not cv
and
is true
.
Effects:
If
, assigns
as if
constructing an object of type
with the arguments
otherwise if
is true
-
destroys
by callingunex
,unexpect . ~ unexpected < E > () -
initializes
as if direct-non-list-initializing an object of typeval
withT
andstd :: forward < Args > ( args )... -
set
tohas_val true
;
otherwise if
is true
-
constructs a
fromT tmp
(which can throw),std :: forward < Args > ( args )... -
destroys
by callingunex
,unexpect . ~ unexpected < E > () -
initializes
as if direct-non-list-initializing an object of typeval
withT
(which cannot throw) andstd :: move ( tmp ) -
set
tohas_val true
;
otherwise
-
move constructs an
fromunexpected < E > tmp
,unexpected ( this -> error ()) -
destroys
by callingunex
,unexpect . ~ unexpected < E > () -
initializes
as if direct-non-list-initializing an object of typeval
withT
. Either,std :: forward < Args > ( args )... -
the constructor didn’t throw, set
tohas_val true
, or -
the constructor did throw, so move-construct the
fromunexpected < E >
back into the expected storage (which can’t throw astmp
is nothrow-move-constructible), and re-throw the exception.E
-
Ensures:
is true
.
Returns: A reference to the new contained value
.
Throws: Any exception thrown by the operations specified in the effect clause.
Remarks: If an exception is thrown during the call to
's assignment, nothing
changes.
template < class U , class ... Args > T & emplace ( initializer_list < U > il , Args && ... args );
Constraints:
is not cv
and
is true
.
Effects:
If
, assigns
as if
constructing an object of type
with the arguments
otherwise if
is true
-
destroys
by callingunex
,unexpect . ~ unexpected < E > () -
initializes
as if direct-non-list-initializing an object of typeval
withT
andil , std :: forward < Args > ( args )... -
set
tohas_val true
;
otherwise if
is true
-
constructs a
fromT tmp
(which can throw),il , std :: forward < Args > ( args )... -
destroys
by callingunex
,unexpect . ~ unexpected < E > () -
initializes
as if direct-non-list-initializing an object of typeval
withT
(which cannot throw) andstd :: move ( tmp ) -
set
tohas_val true
;
otherwise
-
move constructs an
fromunexpected < E > tmp
,unexpected ( this -> error ()) -
destroys
by callingunex
,unexpect . ~ unexpected < E > () -
initializes
as if direct-non-list-initializing an object of typeval
withT
. Either,il , std :: forward < Args > ( args )... -
the constructor didn’t throw, set
tohas_val true
, or -
the constructor did throw, so move-construct the
fromunexpected < E >
back into the expected storage (which can’t throw astmp
is nothrow-move-constructible), and re-throw the exception.E
-
Ensures:
is true
.
Returns: A reference to the new contained value
.
Throws: Any exception thrown by the operations specified in the effect clause.
Remarks: If an exception is thrown during the call to
's assignment nothing
changes.
5.6. �.�.4.4 Swap [expected.object.swap]
void swap ( expected < T , E >& rhs ) noexcept ( see below );
Constraints:
-
Lvalues of type
areT
; andSwappable -
Lvalues of type
areE
; andSwappable -
isis_void_v < T > true
or
isis_void_v < T > false
and either:-
isis_move_constructible_v < T > true
; or -
isis_move_constructible_v < E > true
.
-
Effects: See Table editor-please-pick-a-number-5
|
| |
| if is not cv calls
| calls
|
|
| calls
|
Throws: Any exceptions that the expressions in the Effects clause throw.
Remarks: The expression inside noexcept is equivalent to:
is true
and
is true
and
is true
and
is true
.
5.7. �.�.4.5 Observers [expected.object.observe]
constexpr const T * operator -> () const ; T * operator -> ();
Constraints:
is not cv
.
Expects:
is true
.
Returns:
.
constexpr const T & operator * () const & ; T & operator * () & ;
Constraints:
is not cv
.
Expects:
is true
.
Returns:
.
constexpr T && operator * () && ; constexpr const T && operator * () const && ;
Constraints:
is not cv
.
Expects:
is true
.
Returns:
.
constexpr explicit operator bool () noexcept ;
Returns:
.
constexpr bool has_value () const noexcept ;
Returns:
.
constexpr void expected < void , E >:: value () const ;
Throws:
if
.
constexpr const T & expected :: value () const & ; constexpr T & expected :: value () & ;
Constraints:
is not cv
.
Returns:
, if
.
Throws:
if
.
constexpr T && expected :: value () && ; constexpr const T && expected :: value () const && ;
Constraints:
is not cv
.
Returns:
, if
.
Throws:
if
.
constexpr const E & error () const & ; constexpr E & error () & ;
Expects:
is false
.
Returns:
.
constexpr E && error () && ; constexpr const E && error () const && ;
Expects:
is false
.
Returns:
.
template < class U > constexpr T value_or ( U && v ) const & ;
Returns:
.
Remarks: If
is true
and
is false
the program is ill-formed.
template < class U > constexpr T value_or ( U && v ) && ;
Returns:
.
Remarks: If
is true
and
is false
the program is ill-formed.
5.8. �.�.4.6 Expected Equality operators [expected.equality_op]
template < class T1 , class E1 , class T2 , class E2 > friend constexpr bool operator == ( const expected < T1 , E1 >& x , const expected < T2 , E2 >& y );
Expects: The expressions
and
are well-formed and their results
are convertible to
.
Returns: If
, false
; otherwise if
,
; otherwise true
if
and
are cv
or
otherwise.
Remarks: Specializations of this function template, for which
and
are cv
or
and
is a core constant expression, are
constexpr functions.
template < class T1 , class E1 , class T2 , class E2 > constexpr bool operator != ( const expected < T1 , E1 >& x , const expected < T2 , E2 >& y );
Mandates: The expressions
and
are
well-formed and their results are convertible to
.
Returns: If
, true
; otherwise if
,
; otherwise true
if
and
are cv
or
.
Remarks: Specializations of this function template, for which
and
are cv
or
and
is a core
constant expression, are constexpr functions.
5.9. �.�.4.7 Comparison with T
[expected.comparison_T]
template < class T1 , class E1 , class T2 > constexpr bool operator == ( const expected < T1 , E1 >& x , const T2 & v ); template < class T1 , class E1 , class T2 > constexpr bool operator == ( const T2 & v , const expected < T1 , E1 >& x );
Mandates:
and
are not cv
and the expression
is well-formed
and its result is convertible to
. [ Note:
need not be EqualityComparable. - end note]
Returns:
.
template < class T1 , class E1 , class T2 > constexpr bool operator != ( const expected < T1 , E1 >& x , const T2 & v ); template < class T1 , class E1 , class T2 > constexpr bool operator != ( const T2 & v , const expected < T1 , E1 >& x );
Mandates:
and
are not cv
and the expression
is well-formed
and its result is convertible to
. [ Note:
need not be EqualityComparable. - end note]
Returns:
.
5.10. �.�.4.8 Comparison with unexpected < E >
[expected.comparison_unexpected_E]
template < class T1 , class E1 , class E2 > constexpr bool operator == ( const expected < T1 , E1 >& x , const unexpected < E2 >& e ); template < class T1 , class E1 , class E2 > constexpr bool operator == ( const unexpected < E2 >& e , const expected < T1 , E1 >& x );
Mandates: The expression
is well-formed and
its result is convertible to
.
Returns:
.
template < class T1 , class E1 , class E2 > constexpr bool operator != ( const expected < T1 , E1 >& x , const unexpected < E2 >& e ); template < class T1 , class E1 , class E2 > constexpr bool operator != ( const unexpected < E2 >& e , const expected < T1 , E1 >& x );
Mandates: The expression
is well-formed and
its result is convertible to
.
Returns:
.
5.11. �.�.4.9 Specialized algorithms [expected.specalg]
template < class T1 , class E1 > void swap ( expected < T1 , E1 >& x , expected < T1 , E1 >& y ) noexcept ( noexcept ( x . swap ( y )));
Constraints:
-
is cvT1
orvoid
isis_move_constructible_v < T1 > true
; and -
isis_swappable_v < T1 > true
; and -
isis_move_constructible_v < E1 > true
; and -
isis_swappable_v < E1 > true
.
Effects: Calls
.
5.12. �.�.5 Unexpected objects [expected.unexpected]
5.13. �.�.5.1 General [expected.unexpected.general]
This subclause describes class template
that
represents unexpected objects.
5.14. �.�.5.2 Class template unexpected
[expected.unexpected.object]
template < class E > class unexpected { public : constexpr unexpected ( const unexpected & ) = default ; constexpr unexpected ( unexpected && ) = default ; template < class ... Args > constexpr explicit unexpected ( in_place_t , Args && ...); template < class U , class ... Args > constexpr explicit unexpected ( in_place_t , initializer_list < U > , Args && ...); template < class Err = E > constexpr explicit unexpected ( Err && ); template < class Err > constexpr explicit ( see below ) unexpected ( const unexpected < Err >& ); template < class Err > constexpr explicit ( see below ) unexpected ( unexpected < Err >&& ); constexpr unexpected & operator = ( const unexpected & ) = default ; constexpr unexpected & operator = ( unexpected && ) = default ; template < class Err = E > constexpr unexpected & operator = ( const unexpected < Err >& ); template < class Err = E > constexpr unexpected & operator = ( unexpected < Err >&& ); constexpr const E & value () const & noexcept ; constexpr E & value () & noexcept ; constexpr const E && value () const && noexcept ; constexpr E && value () && noexcept ; void swap ( unexpected & other ) noexcept ( see below ); template < class E1 , class E2 > friend constexpr bool operator == ( const unexpected < E1 >& , const unexpected < E2 >& ); template < class E1 , class E2 > constexpr bool operator != ( const unexpected < E1 >& , const unexpected < E2 >& ); template < class E1 > friend void swap ( unexpected < E1 >& x , unexpected < E1 >& y ) noexcept ( noexcept ( x . swap ( y ))); private : E val ; // exposition only }; template < class E > unexpected ( E ) -> unexpected < E > ;
A program that instantiates the definition of
for a non-object
type, an array type, a specialization of
or an cv-qualified type is
ill-formed.
5.14.1. �.�.5.2.1 Constructors [expected.unexpected.ctor]
template < class Err > constexpr explicit unexpected ( Err && e );
Constraints:
-
isis_constructible_v < E , Err > true
; and -
isis_same_v < remove_cvref_t < U > , in_place_t > false
; and -
isis_same_v < remove_cvref_t < U > , unexpected > false
.
Effects: Initializes
as if direct-non-list-initializing an
object of type
with the expression
.
Throws: Any exception thrown by the selected constructor of
.
Remarks: If
’s selected constructor is a constant subexpression, this
constructor is a constexpr constructor.
template < class ... Args > constexpr explicit unexpected ( in_place_t , Args && ...);
Constraints:
is true
.
Effects: Initializes
as if direct-non-list-initializing an
object of type
with the arguments
.
Throws: Any exception thrown by the selected constructor of
.
Remarks:
If
’s selected constructor is a constant subexpression, this constructor is a constexpr constructor.
template < class U , class ... Args > constexpr explicit unexpected ( in_place_t , initializer_list < U > , Args && ...);
Constraints:
is true
.
Effects: Initializes
as if direct-non-list-initializing an
object of type
with the arguments
.
Throws: Any exception thrown by the selected constructor of
.
Remarks:
If
’s selected constructor is a constant subexpression, this constructor is a constexpr constructor.
template < class Err > constexpr explicit ( see below ) unexpected ( const unexpected < Err >& e );
Constraints:
-
isis_constructible_v < E , const Err &> true
; and -
isis_constructible_v < E , unexpected < Err >&> false
; and -
isis_constructible_v < E , unexpected < Err >> false
; and -
isis_constructible_v < E , const unexpected < Err >&> false
; and -
isis_constructible_v < E , const unexpected < Err >> false
; and -
isis_convertible_v < unexpected < Err >& , E > false
; and -
isis_convertible_v < unexpected < Err > , E > false
; and -
isis_convertible_v < const unexpected < Err >& , E > false
; and -
isis_convertible_v < const unexpected < Err > , E > false
.
Effects: Initializes
as if direct-non-list-initializing an
object of type
with the expression
.
Throws: Any exception thrown by the selected constructor of
.
Remarks: The expression inside
is equivalent to
.
template < class Err > constexpr explicit ( see below ) unexpected ( unexpected < Err >&& e );
Constraints:
-
isis_constructible_v < E , Err > true
; and -
isis_constructible_v < E , unexpected < Err >&> false
; and -
isis_constructible_v < E , unexpected < Err >> false
; and -
isis_constructible_v < E , const unexpected < Err >&> false
; and -
isis_constructible_v < E , const unexpected < Err >> false
; and -
isis_convertible_v < unexpected < Err >& , E > false
; and -
isis_convertible_v < unexpected < Err > , E > false
; and -
isis_convertible_v < const unexpected < Err >& , E > false
; and -
isis_convertible_v < const unexpected < Err > , E > false
.
Effects: Initializes
as if direct-non-list-initializing an
object of type
with the expression
.
Throws: Any exception thrown by the selected constructor of
.
Remarks: The expression inside
is equivalent to
.
5.14.2. �.�.5.2.2 Assignment [expected.unexpected.assign]
template < class Err = E > constexpr unexpected & operator = ( const unexpected < Err >& e );
Constraints:
is true
.
Effects: Equivalent to
.
Returns:
.
Remarks: If
’s selected assignment operator is a constant subexpression,
this function is a constexpr function.
template < class Err = E > constexpr unexpected & operator = ( unexpected < Err >&& e );
Constraints:
is true
.
Effects: Equivalent to
.
Returns:
.
Remarks: If
’s selected assignment operator is a constant subexpression,
this function is a constexpr function.
5.14.3. �.�.5.2.3 Observers [expected.unexpected.observe]
constexpr const E & value () const & ; constexpr E & value () & ;
Returns:
.
constexpr E && value () && ; constexpr const E && value () const && ;
Returns:
.
5.14.4. �.�.5.2.4 Swap [expected.unexpected.ctor]
void swap ( unexpected & other ) noexcept ( see below );
Mandates:
is true
.
Effects: Equivalent to
.
Throws: Any exceptions thrown by the operations in the relevant part of [expected.swap].
Remarks: The expression inside
is equivalent to:
is true
.
5.15. �.�.5.2.5 Equality operators [expected.unexpected.equality_op]
template < class E1 , class E2 > friend constexpr bool operator == ( const unexpected < E1 >& x , const unexpected < E2 >& y );
Returns:
.
template < class E1 , class E2 > friend constexpr bool operator != ( const unexpected < E1 >& x , const unexpected < E2 >& y );
Returns:
.
5.15.1. �.�.5.2.5 Specialized algorithms [expected.unexpected.specalg]
template < class E1 > friend void swap ( unexpected < E1 >& x , unexpected < E1 >& y ) noexcept ( noexcept ( x . swap ( y )));
Constraints:
is true
.
Effects: Equivalent to
.
5.16. �.�.6 Template Class bad_expected_access
[expected.bad_expected_access]
template < class E > class bad_expected_access : public bad_expected_access < void > { public : explicit bad_expected_access ( E ); virtual const char * what () const noexcept override ; E & error () & ; const E & error () const & ; E && error () && ; const E && error () const && ; private : E val ; // exposition only };
The template class
defines the type of objects thrown as
exceptions to report the situation where an attempt is made to access the value
of
object that contains an
.
bad_expected_access :: bad_expected_access ( E e );
Effects: Initializes
with
.
Ensures:
returns an implementation-defined NTBS.
const E & error () const & ; E & error () & ;
Returns:
E && error () && ; const E && error () const && ;
Returns:
virtual const char * what () const noexcept override ;
Returns: An implementation-defined NTBS.
5.17. �.�.7 Class bad_expected_access < void >
[expected.bad_expected_access_base]
template <> class bad_expected_access < void > : public exception { public : explicit bad_expected_access (); };
5.18. �.�.8 unexpect
tag [expected.unexpect]
struct unexpect_t ( see below );
inline constexpr unexpect_t unexpect ( unspecified );
Type
does not have a default constructor or an initializer-list
constructor, and is not an aggregate.
5.19. Feature test macro
The proposed library feature test macro is
.
6. Implementation & Usage Experience
There are multiple implementations of
as specified in this paper, listed below. There are also many implementations which are similar but not the same as specified in this paper, they are not listed below.
6.1. Sy Brand
By far the most popular implementation is Sy Brand’s, with over 500 stars on GitHub and extensive usage.
- Code: https://github.com/TartanLlama/expected
-
Non comprehensive usage list:
- Telegram desktop client
- Ceph distributed storage system
- FiveM and RedM mod frameworks
- Rspamd spam filtering system
- OTTO hardware synth
- Some NIST project
- about 10 cryptocurrency projects
-
Testimonials:
-
https://twitter.com/syoyo/status/1328196033545814016
Ultra super cooooooooooooooooooooooooooooooooopooool!!!!! 🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🙏🙏🙏🙏🙏🙏🙏🙏🙏☺️☺️☺️☺️☺️☺️☺️☺️🥰🥰🥰🥰🥰🥰🥰😍😍😍😍😍😍😍😍😍 C++11/14/17 std::expected with functional-style extensions
-
https://twitter.com/LesleyLai6/status/1328199023786770432
I use @TartanLlama 's optional and expected libraries for almost all of my projects. Thay are amazing!
Though I made a custom fork and added a few rust Rusult like features. -
https://twitter.com/bjorn_fahller/status/1229803982685638656
I used tl::expected<> and a few higher order functions to extend functionality with 1/3 code size ;-)
-
https://twitter.com/toffiloff/status/1101559543631351808
Also, using @TartanLlama’s ‘expected’ library has done wonders for properly handling error cases on bare metal systems without exceptions enabled
-
https://twitter.com/chsiedentop/status/1296624103080640513
I can really recommend the tl::expected library which has this 😉. BTW, great library, @TartanLlama!
-
https://twitter.com/syoyo/status/1328196033545814016
6.2. Vicente J. Botet Escriba
The original author of
has an implementation available:
6.3. WebKit
The WebKit web browser (used in Safari) contains an implementation that’s used throughout its codebase.