Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
2945 lines (2109 sloc) 121 KB
Document number: P0323R2
Date: 2017-06-15
Revises: N4109/N4015
Project: ISO/IEC JTC1 SC22 WG21 Programming Language C++
Audience: Library Evolution Working Group
Reply-to: Vicente J. Botet Escribá <vicente.botet@nokia.com>

A proposal to add a utility class to represent expected object (Revision 4)

====================================================================

Abstract

Class template expected<T,E> proposed here is a type that may contain a value of type T or a value of type E in its storage space. T represents the expected value, E represents the reason explaining why it doesn’t contains a value of type T. The interface and the rational are based on std::optional N3793. We can consider expected<T,E> as a generalization of optional<T>.

Table of Contents

History

Revision 4 - Revision of P0323R1

The 4th revision of this proposal aligns with the late optional changes, complete the wording ensuring the never empty warranties and fixes some typos. In addition it adds more open points concerning whether expected<T,E> must be ordered and implicit conversions. Most of the changes come from suggestion done by Niall and from the feedback of the review of the Boost.Outcome library and my understanding of the use cases the Boost.Outcome reveal.

Next follows the main modifications:

  • Added rvalue overloads for bad_expected_access::error() getters and remove the constexpr.
  • Provide factories that returns expected<const T,E>.
  • Adapt from late changes in optional concerning the observers.
  • get_unexpected() returns by reference now.
  • Allow construction from expected<U,G> when the types are convertible.
  • Make bad_expected_access inherit from std::exception instead of from `std::logic_error.
  • Take in account constructor guides.

More open points:

  • Consider removing make_expected_from_call.
  • Consider adding a level on the bad_expect_access<E> exception hierarchy.
  • Consider changing the default Error argument to error_code.
  • Consider removing the comparison operators and specialize less<>.
  • Consider expected<T&, E>.
  • Consider a function that allows to adapt the error transported by expected.
  • Reconsider expected with a variadic number of errors.

Revision 3 - Revision of P0323R0 after with feedback from Oulu

The 3rd revision of this proposal fixes some typos and takes in account the feedback from Oulu meeting. Next follows the direction of the committee:

  • Split the proposal on a simple expected class and a generic monadic interface.
  • As variant, expected requires some properties in order to ensure the never-empty warranties. As the error type should be no throw movable, we are always sure to be able to ensure the never-empty warranties (Wording not yet complete).
  • Removed exception_ptr specializations as it introduces different behavior, in particular comparisons, exception thrown, ....
  • Adapted comparisons to P0393R3 and consider T < E to be inline with variant.
  • Redefined the meaning of e = {} as expected<T,E> defaults to T().
  • Added const && overloads for value getters.
  • Consider to adapt the constructor and assignment from convertible to T and E to follow last changes in std::optional (Wording not yet complete).
  • Considered making the conversion from the value type explicit and remove the mixed operations to make the interface more robust even if less friendly.
  • Removed the future work section.

Revision 2 - Revision of N4109 after discussion on the ML

  • Fix default constructor to T. N4109 should change the default constructor to T, but there were some inconsistencies.
  • Complete wording comparison.
  • Adapted to last version of referenced proposals.
  • Moved alternative designs from open questions to an Appendix.
  • Moved already answered open points to a Rationale section.
  • Moved open points that can be decided later to a future directions section.
  • Complete wording hash.
  • Add a section for adapting to await.
  • Add a section in future work about a possible variadic.
  • Fix minor typos.

Not done yet

  • As variant, expected requires some properties in order to ensure the never-empty warranties. Add more on never-empty warranties and replace the wording.

Revision 1 - Revision of N4015 after Rapperswil feedback:

  • Switch the expected class template parameter order from expected<E,T> to expected<T,E>.
  • Make the unexpected value a salient attribute of the expected class concerning the relational operators.
  • Removed open point about making expected<T,E> and expected different classes.

Introduction

Class template expected<T,E> proposed here is a type that may contain a value of type T or a value of type E in its storage space. T represents the expected value, E represents the reason explaining why it doesn’t contains a value of type T, that is, the unexpected value. Its interface allows to query if the underlying value is either the expected value (of type T) or an unexpected value (of type E). The original idea comes from Andrei Alexandrescu C++ and Beyond 2012: Systematic Error Handling in C++ talk Alexandrescu.Expected. The interface and the rational are based on std::optional N3793. We can consider that expected<T,E> is a generalization of optional<T> providing in addition some specific functions associated to the unexpected type E. It requires no changes to core language, and breaks no existing code.

There is a related proposal for a class including a status and an optional value P0262R0. P0157R0 describes when to use each of the different error report mechanism.

Motivation

Basically, the two main error mechanisms are exceptions and return codes. Before further explanation, we should ask us what are the characteristics of a good error mechanism.

  • Error visibility: Failure cases should appear throughout the code review. Because the debug can be painful if the errors are hidden.
  • Information on errors: The errors should carry out as most as possible information from their origin, causes and possibly the ways to resolve it.
  • Clean code: The treatment of errors should be in a separate layer of code and as much invisible as possible. So the code reader could notice the presence of exceptional cases without stop his reading.
  • Non-Intrusive error The errors should not monopolize a communication channel dedicated to the 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 to be quite contradictory and deserve further explanation. The former points out that errors not handled should appear clearly in the code. The latter tells us that the error handling mustn’t interfere with the code reading, meaning that it clearly shows the normal execution flow. A comparison between the exception and return codes is given in the next table.

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 provides richer error code.
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.

Expected class

We can do the same analysis for the expected<T, E> 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 expected<T,E> and the user cannot ignore the error case if he wants to retrieve the contained value.
  • Information: Arbitrarily rich.
  • Clean code: The monadic interface of expected provides a framework delegating the error handling to another layer of code. Note that expected<T,E> can also act as a bridge between an exception-oriented code and a nothrow world.
  • Non-Intrusive Use the return channel without monopolizing it.

It worths mentioning the other characteristics of expected<T,E>:

  • Associates errors with computational goals.
  • Naturally allows multiple errors inflight.
  • Teleportation possible.
  • Across thread boundaries.
  • Across no-throw subsystem boundaries.
  • Across time: save now, throw later.
  • Collect, group, combine errors.

Use cases

Safe division

This example shows how to define a safe divide operation checking for divide-by-zero conditions. Using exceptions, we might write something like this:

struct DivideByZero: public std::exception {...};
double safe_divide(double i, double j)
{
	if (j==0) throw DivideByZero();
	else return i / j;
}

With expected<T,E>, we are not required to use exceptions, we can use std::error_condition which is easier to introspect than std::exception_ptr if we want to use the error. For the purpose of this example, we use the following enumeration (the boilerplate code concerning std::error_condition is not shown):

enum class arithmetic_errc
{
	divide_by_zero, // 9/0 == ?
	not_integer_division // 5/2 == 2.5 (which is not an integer)
};

Using expected<double, error_condition>, the code becomes:

expected<double,error_condition> safe_divide(double i, double j)
{
	if (j==0) return make_unexpected(arithmetic_errc::divide_by_zero); // (1)
	else return i / j; // (2)
}

(1) The implicit conversion from unexpected_type<E> to expected<T,E> and (2) from T to expected<T,E> prevents using too much boilerplate code. The advantages are that we have a clean way to fail without using the exception machinery, and we can give precise information about why it failed as well. The liability is that this function is going to be tedious to use. For instance, the exception-based

function i + j/k is:
double f1(double i, double j, double k)
{
	return i + safe_divide(j,k);
}

but becomes using expected<double, error_condition>:

expected<double, error_condition> f1(double i, double j, double k)
{
	auto q = safe_divide(j, k)
	if (q) return i + *q;
	else return q;
}

We can use expected<T, E> to represent different error conditions. For instance, with integer division, we might want to fail if the two numbers are not evenly divisible as well as checking for division by zero. We can overload our safe_divide function accordingly:

expected<int, error_condition> safe_divide(int i, int j)
{
	if (j == 0) return make_unexpected(arithmetic_errc::divide_by_zero);
	if (i%j != 0) return make_unexpected(arithmetic_errc::not_integer_division);
	else return i / j;
}

Error retrieval and correction

The major advantage of expected<T,E> over optional<T> is the ability to transport an error, but we didn’t come yet to an example that retrieve the error. First of all, we should wonder what a programmer do when a function call returns an error:

  1. Ignore it.
  2. Delegate the responsibility of error handling to higher layer.
  3. Trying to resolve the error.

Because the first behavior might lead to buggy application, we won’t consider it in a first time. The handling is dependent of the underlying error type, we consider the exception_ptr and the error_condition types.

We spoke about how to use the value contained in the expected but didn’t discuss yet the error usage.

A first imperative way to use our error is to simply extract it from the expected using the error() member function. The following example shows a divide2 function that return 0 if the error is divide_by_zero:

expected<int, error_condition> divide2(int i, int j)
{
	auto e = safe_divide(i,j);
	if (!e && e.error().value() == arithmetic_errc::divide_by_zero) {
		return 0;
	}
	return e;
}

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++ 17.

Design rationale

The same rationale described in N3672 for optional<T> applies to expected<T,E> and expected<T, nullopt_t> should behave almost as optional<T> with some exceptions. That is, we see expected<T,E> as optional<T> for which all the values of E collapse into a single value nullopt. In the following sections we present the specificities of the rationale in N3672 applied to expected<T,E>.

Conceptual model of expected<T,E>

expected<T,E> models a discriminated union of types T and unexpected_type<E>. expected<T,E> is viewed as a value of type T or value of type unexpected_type<E>, allocated in the same storage, along with the way of determining which of the two it is.

The interface in this model requires operations such as comparison to T, comparison to E, 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 (T or E) and either the value of T or the value of E.

Additionally, within the affordable limits, we propose the view that expected<T,E> extends the set of the values of T by the values of type E. This is reflected in initialization, assignment, ordering, and equality comparison with both T and E. In the case of optional<T>, T cannot be a nullopt_t. As the types T and E could be the same in expected<T,E>, there is need to tag the values of E to avoid ambiguous expressions. The make_unexpected(E) function is proposed for this purpose. However T cannot be unexpected_type<E> for a given E.

expected<int, string> ei = 0;
expected<int, string> ej = 1;
expected<int, string> ek = make_unexpected(string());

ei = 1;
ej = make_unexpected(E());;
ek = 0;

ei = make_unexpected(E());;
ej = 0;
ek = 1;

Initialization of expected<T,E>

In cases T and E have value semantic types capable of storing n and m distinct values respectively, expected<T,E> can be seen as an extended T capable of storing n + m values: these T and E stores. Any valid initialization scheme must provide a way to put an expected object to any of these states. In addition, some T’s aren't CopyConstructible and their expected variants still should be constructible with any set of arguments that work for T.

As in N3672, the model retained is to initialize either by providing an already constructed T or a tagged E. The default constructor required T to be default-constructible (as expected<T> should behave as T as much as possible).

string s"STR";

expected<string, error_condition> es{s}; // requires Copyable<T>
expected<string, error_condition> et = s; // requires Copyable<T>
expected<string, error_condition> ev = string"STR"; // requires Movable<T>

expected<string, error_condition> ew; // expected value
expected<string, error_condition> ex{}; // expected value
expected<string, error_condition> ey = {}; // expected value
expected<string, error_condition> ez = expected<string,error_condition>{}; // expected value

In order to create an unexpected object, the special function make_unexpected needs to be used:

expected<string, int> ep{make_unexpected(-1)}; // unexpected value, requires Movable<E>
expected<string, int> eq = make_unexpected(-1); // unexpected value, requires Movable<E>

As in N3672, and in order to avoid calling move/copy constructor of T, we use a “tagged” placement constructor:

expected<MoveOnly, error_condition> eg; // expected value
expected<MoveOnly, error_condition> eh{}; // expected value
expected<MoveOnly, error_condition> ei{in_place}; // calls MoveOnly{} in place
expected<MoveOnly, error_condition> ej{in_place, "arg"}; // calls MoveOnly{"arg"} in place

To avoid calling move/copy constructor of E, 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 in_place that is coherent with unexpect could be expect. Being compatible with optional<T> seems more important. So this proposal doesn’t propose such a expect tag.

The alternative and also comprehensive initialization approach, which is compatible with the default construction of expected<T,E> as T(), could have been a variadic perfect forwarding constructor that just forwards any set of arguments to the constructor of the contained object of type T.

Never-empty guaranty

As boost::variant<T,unexpected_type<E>>, expected<T,E> ensures that it is never empty. All instances v of type expected<T,E> guarantee v has constructed content of one of the types T or E, even if an operation on v has previously failed.

This implies that expected may be viewed precisely as a union of exactly its bounded types. This “never-empty” property insulates the user from the possibility of undefined expected content or an expected valueless_by_exception as std::variant and the significant additional complexity-of-use attendant with such a possibility.

In order to ensure this property the types T and E must satisfy some requirements as described in P0110R0. Given the nature of the parameter E, that is, to transport an error, it is expected that is_nothrow_copy_constructible<E>, is_nothrow_move_constructible<E>, is_nothrow_copy_assignable<E> and is_nothrow_move_assignable<E>.

Note however that these constraints are applied only to the operations that need them.

If is_nothrow_constructible<T, Args...> is false expected<T,E>::emplace(Args...) function is not defined. In this case, it is the responsibility of the user to create a temporary and move or copy it.

The default constructor

Similar data structure includes optional<T>, variant<T1,...,Tn> and future<T>. We can compare how they are default constructed.

  • std::optional<T> default constructs to an optional with no value.
  • std::variant<T1,...,Tn> default constructs to T1 if default constructible or it is ill-formed
  • std::future<T> default constructs to an invalid future with no shared state associated, that is, no value and no exception.
  • std::optional<T> default constructor is equivalent to boost::variant<nullopt_t, T>.

It raises several questions about expected<T,E>:

  • Should the default constructor of expected<T,E> behave like variant<T, unexpected_type<E>> or as variant<unexpected_type<E>,T>?
  • Should the default constructor of expected<T, nullopt_t> behave like optional<T>? If yes, how should behave the default constructor of expected<T,E>? As if initialized with make_unexpected(E())? This would be equivalent to the initialization of variant<unexpected_type<E>,T>.
  • Should expected<T,E> provide a default constructor at all? N3527 presents valid arguments against this approach, e.g. array<expected<T,E>> would not be possible.

Requiring E to be default constructible seems less constraining than requiring T to be default constructible (e.g. consider the Date example in N3527). With the same semantics expected<Date,E> would be Regular with a meaningful not-a-date state created by default.

The authors consider the arguments in N3527 valid for optional<T> and expected<T,E>, however the committee as requested it the paper proposes that expected<T,E> default constructor should behave as constructed with T() if T is default constructible.

Conversion from T

An object of type T is implicitly convertible to an expected object of type expected<T,E>:

expected<int> ei = 1; // works

This convenience feature is not strictly necessary because you can achieve the same effect by using tagged forwarding constructor:

expected<int> ei{in_place, 1};

If the latter appears too cumbersome, one can always use function make_expected described below:

expected<int> ei = make_expected(1);
auto ej = make_expected(1);

or simply using deduced template parameter for constructors

expected<int> ei = expected(1);
auto ej = expected(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 expected_type<T> (similar to unexpected_type<E> explicitly convertible from T and implicitly convertible to expected<T,E>.

expected<int> ei = make_expected(1);
expected<int> ej = make_unexpected(ec);

While 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 std::optional has already be delivered in C++17 and it has this gotcha.

Conversion from E

An object of type E is not convertible to an unexpected object of type expected<T,E> since E and T can be of the same type. The proposed interface uses a special tag unexpect and a special non-member make_unexpected function to indicate an unexpected state for expected<T,E>. It is used for construction and assignment. This might rise a couple of objections. First, this duplication is not strictly necessary because you can achieve the same effect by using the unexpect tag forwarding constructor:

expected<string, int> exp1 = make_unexpected(1);
expected<string, int> exp2 = {unexpect, 1};
exp1 = make_unexpected(1);
exp2 = {unexpect, 1};

or simply using deduced template parameter for constructors

expected<string, int> exp1 = unexpected_type(1);
exp1 = unexpected_type(1);

While some situations would work with the {unexpect, ...} syntax, using make_unexpected makes the programmer’s intention as clear and less cryptic. Compare these:

expected<vector<int>, int> get1() {}
	return {unexpect, 1};
}
expected<vector<int>, int> get2() {
	return make_unexpected(1);
}
expected<vector<int>, int> get3() {
	return expected<vector<int>, int>{unexpect, 1};
}
expected<vector<int>, int> get2() {
	return unexpected_type(1);
}

The usage of make_unexpected is also a consequence of the adapted model for expected: a discriminated union of T and unexpected_type<E>.

Should we support the exp2 = {}?

Note also that the definition of unexpected_type has an explicitly deleted default constructor. This was in order to enable the reset idiom exp2 = {} which would otherwise not work due to the ambiguity when deducing the right-hand side argument.

Now that expected<T,E> defaults to T{} the meaning of exp2 = {} is to assign T{}.

Observers

In order to be as efficient as possible, this proposal includes observers with narrow and wide contracts. Thus, the value() function has a wide contract. If the expected object doesn’t contain a value, an exception is thrown. However, when the user knows that the expected object is valid, the use of operator* would be more appropriated.

Explicit conversion to bool

The rational described in N3672 for optional<T> applies to expected<T,E> and so, the following example combines initialization and value-checking in a boolean context.

if (expected<char, error_condition> ch = readNextChar()) {
// ...
}

has_value() following P0032

has_value() has been added to follow [P0032R2].

Accessing the contained value

Even if expected<T,E> has not been used in practice for enough time as std::optional or Boost.Optional, we consider that following the same interface as std::optional<T> makes the C++ standard library more homogeneous.

The rational described in N3672 for optional<T> applies to expected<T,E>.

Dereference operator

It was chosen to use indirection operator because, along with explicit conversion to bool, 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 optional; it clearly indicates the fact that the value may be missing and that we return a reference rather than a value. The indirection operator has risen some objections because it may incorrectly imply expected and optional 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, expected as well as optional 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 T 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 doesn’t contain a value is undefined behavior. This behavior offers maximum runtime performance.

Function value

In addition to the indirection operator, we propose the member function value 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 bad_expected_access<E> (derived from std::logic_error) which will contain the stored error.

bad_expected_access<E> and bad_optional_access could inherit both from a bad_access exception derived from logic_error, but this is not proposed yet.

Accessing the contained error

Usually, accessing the contained error is done once we know the expected object has no value. This is why the error() function has a narrow contract: it works only if ! bool(*this).

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 value_or() method since we want to return 0 only if the error is equal to empty_stream.

We could as well provide an error access function with a wide contract. We just need to see how to name each one.

Conversion to the unexpected value

As the error() function, the get_unexpected() works only if the expected object has no value. It is used to propagate errors. Note that the following equivalences yield:

f.get_unexpected() == make_unexpected(f.error());
f.get_unexpected() == expected<T, E>{unexpect, f.error()};

This member is provided for convenience, it is further demonstrated in the next example:

expected<pair<int, int>, errc> getIntRange(istream_range& r) {
	auto f = getInt(r);
	if (!f) return f.get_unexpected();
	auto m = matchedString("..", r);
	if (!m) return m.get_unexpected();
	auto l = getInt(r);
	if (!l) return l.get_unexpected();
	return std::make_pair(*f, *l);
}

get_unexpected is also provided for symmetry purposes. On one side, there is an implicit conversion from unexpected_type<E> to expected<T,E> and on the other side there is an explicit conversion from expected<T,E> to unexpected_type<E> via this function.

Function value_or

The function member value_or() has the same semantics than optional N3672 since the type of E doesn’t matter; hence we can consider that E == nullopt_t and the optional semantics yields.

This function is a convenience function that should be a non-member function for optional and expected, however as it is already part of the optional interface we propose to have it also for expected.

Relational operators

As optional and variant, one of the design goals of expected is that objects of type expected<T,E> should be valid elements in STL containers and usable with STL algorithms (at least if objects of type T and E are). Equality comparison is essential for expected<T,E> to model concept Regular. C++ does not have concepts yet, but being regular is still essential for the type to be effectively used with STL.

Ordering is essential if we want to store expected values in ordered associative containers. A number of ways of including the unexpected state in comparisons have been suggested. The ones proposed, follows this proposal P0393R3: unexpected values stored in expected<T,E> are simply treated as additional values that are always different from T; these values are always compared as greater than any value of T when stored in an expected object. This is because we see expected<T,E> as a specialization of variant<T, unexpected_type<E>

But how to define the relational operators for unexpected_type<E>? We can forward the request to the respective E relational operators when E defines these operators, don't support the operators if unexpected_type<E> doesn't define operator<() operator<().

This limitation is one of the main motivations for having a user defined type E with strict weak ordering. E.g. if the user know the exact types of the exceptions that can be thrown E1, ..., En, the error parameter could be some kind of std::variant<E1, ... En> for which a strict weak ordering can be defined. If the user would like to take care of unknown exceptions something like std::variant<std::monostate, E1, ... En> would be a quite appropriated model.

expected<unsigned, int> e0{0};
expected<unsigned, int> e1{1};
expected<unsigned, int> eN{unexpect, -1};
assert (eN > e0);
assert (e0 > e1);
assert (!(eN > eN));
assert (!(e1 > e1))
assert (eN != e0);
assert (e0 != e1);
assert (eN == eN);
assert (e0 == e0);

Given that both unexpected_type<E> and T are implicitly convertible to expected<T,E>, this implies the existence and semantics of mixed comparison between expected<T,E> and T, as well as betweenexpected<T,E>andunexpected_type`:

assert (eN == make_unexpected(1));
assert (e0 != make_unexpected(1));
assert (eN != 1);
assert (e1 == 1);
assert (eN > 1);
assert (e0 > make_unexpected(1));

Although it is difficult to imagine any practical use case of ordering relation between expected<T,E> and unexpected_type<E>, we still provide it for completeness sake as we have for optional and because the operation can be implemented more efficiently. Note however std::variant doesn't defines these mixed operations.

The mixed relational operators, especially those representing order, between expected<T,E> and T have been accused of being dangerous. In code examples like the following, it may be unclear if the author did not really intend to compare two T’s.

auto count = get_expected_count();
if (count < 20) {} // or did you mean: *count < 20 ?
if (! count || *count < 20) {} // verbose, but unambiguous

Given that expected<T,E> is comparable and implicitly constructible from T, the mixed comparisons are there already. We would have to artificially create the mixed overloads only for them to cause controlled compilation errors. A consistent approach to prohibiting mixed relational operators would be to also prohibit the conversion from T or to also prohibit homogenous relational operators for expected<T,E> ; we do not want to do either, for other reasons discussed in this proposal. Also, mixed relational operations are available in std::optional<T>. Note however that expected<T,nullopt_t> and optional<T> behave the opposite. This is a consequence of having reverted the T and E parameters and so defaulting to T().

Modifiers

Reseting the value

Reseting the value of expected<T,E> is similar to optional<T> but instead of building a disengaged optional<T>, we build an erroneous expected<T,E>. Hence, the semantics and rationale is the same than in N3672.

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 expected’s constructor into the constructor of T.

In order to trigger this constructor one has to use the tag in_place. We need the extra tag to disambiguate certain situations, like calling expected’s default constructor and requesting T’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)

Tag unexpect

This proposal provides an "unexpect" constructor that forwards (perfectly) the arguments provided to expected’s constructor into the constructor of E. In order to trigger this constructor one has to use the tag unexpect.

We need the extra tag to disambiguate certain situations, notably if T and E 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.

Requirements on T and E

Class template expected imposes little requirements on T and E: they have to be complete object type satisfying the requirements of Destructible. Each operations on expected<T,E> have different requirements and may be disable if T or E doesn’t respect these requirements. For example, expected<T,E>’s move constructor requires that T and E are MoveConstructible, expected<T,E>’s copy constructor requires that T and E are CopyConstructible, and so on. This is because expected<T,E> is a wrapper for T or E: it should resemble T or E as much as possible. If T and E are EqualityComparable then (and only then) we expect expected<T,E> to be EqualityComparable.

However in order to ensure the never empty warranties, expected<T,E> requires E to be no throw move constructible. This is normal as the E stands for an error, and throwing while reporting an error is a very bad thing.

Expected references

This proposal doesn’t include expected references as optional [C++17] doesn’t include references neither.

We need a future proposal.

Expected void

While it could seem weird to instantiate optional with void, it has more sense for expected as it conveys in addition, as future<T>, an error state.

Making expected a literal type

In N3672, they propose to make optional a literal type, the same reasoning can be applied to expected. Under some conditions, such that T and E are trivially destructible, and the same described for optional, we propose that expected be a literal type.

Moved from state

We follow the approach taken in optional N3672. Moving expected<T,E> do not modify the state of the source (valued or erroneous) of expected and the move semantics is up to T or E.

IO operations

For the same reasons than optionalN3672 we do not add operator<< and operator>>IO operations.

What happens when E is a status ?

When E is a status, as most of the error codes are, and has more than one value that mean success, setting an expected<T,E> with a successful e value could be misleading if the user expect in this case to have also a T. In this case the user should use the proposed status_value<E,T> class. However, if there is only one value e that mean success, there is no such need and expected<T,E> compose better with the monadic interface P0650R0.

Do we need an expected<T,E>::error_or function?

It has been argued that the error should be always available and that often there is a success value associated to the error.

expected<T,E> would be seen more like something like struct {E; optional<T>}.

The following code show a use case

auto e = function();
switch (e.status())
	success: ....; break;
	too_green: ....; break;
	too_pink: ....; break;

With the current interface the user could be tempted to do

auto e = function();
if (e)
	/*success:*/ ....;
else
	switch (e.error())
	case too_green: ....; break;
	case too_pink: ....; break;

This could be done with the current interface as follows

auto e = function();
switch (error_or(e, success))
	success: ....; break;
	too_green: ....; break;
	too_pink: ....; break;

where

template <class E, class T>
E error_or(expected<T,E> const& e, E err) {
	if (e) return err;
	else return e.error();
}

Do we need to add such an error_or function? as member?

Do we need a expected<T,E>::has_error function?

Another use case which could look much uglier is if the user had to test for whether or not there was a specific error code.

auto e = function();
while ( e.status == timeout ) {
	sleep(delay);
	delay *=2;
	e = function();
}

Here we have a value or a hard error. This use case would need to use something like has_error

e = function();
while ( has_error(e, timeout) )
{
	sleep(delay);
	delay *=2;
	e = function();
}

where

template <class T, class E>
bool has_error(expected<T,E> const& e, E err) {
	if (e) return false;
	else return e.error()==err;
}

Do we want to add such a has_error function? as member?

Do we need a expected<T,G>::adapt_error(function<E(G)) function?

We have the constructor expected<T,E>(expected<T,G>) that allows to transport EXPLICITLY the contained error as soon as it is convertible.

However sometimes we cannot change neither 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 adapt_error function? as member?

Open points

The authors would like to have an answer to the following points if there is any interest at all in this proposal:

Should expected<T,E> be explicitly convertible from T and implicitly convertible from expected_type<T>?

expected<T,E> is implicitly convertible from T and unexpected_type<E>, where unexpected_type<E> is explicitly convertible from E. We could do the same for T and make it implicitly convertible for expected_type<E>.

While implicit conversion from T seems to be friendly, there are cases where this implicit conversion raise some surprises (See a-gotcha-with-optional)

Should expected<T,E> be comparable?

Aside this a-gotcha-with-optional, do we really expected to be comparable, T and E are comparable?

What about changing the default Error argument to error_code?

Niall has suggested that std::error_code is a better default for the expected Error parameter.

Should expected<T,E> throw E instead of bad_expected_access<E>?

As any type can be thrown as an exception, should expected<T,E> throw E instead of bad_expected_access<E>?

Some argument that standard function should throw exceptions that inherit from std::exception, but here the exception throw is given by the user via the type E, it is not the standard library that throws explicitly an exception that don't inherit from std::exception.

This could be convenient as the user will have directly the E exception. However it will be more difficult to identify that this was due to a bad expected access.

If yes, should optional<T> throw nullopt_t instead of bad_optional_access to be coherent?

We don't propose this.

Other have suggested to throw system_error if E is error_code, rethrow if E is exception_ptr, E if it inherits from std::exception and bad_expected_access<E> otherwise.

An alternative would be to add some customization point that state which exception is thrown. See the Appendix I.

What about having a common bad_expect_access_base?

This has the advantage to make it easier for the user to manage with any bad access to expected when the user doesn't care of the error.

The same argument can be seen as a bad thing. It is too easy to ignore the error.

An alternative is to inherit bad_expect_access<E> from bad_expect_access<void> and default the E parameter to void.

Should get_unexpected() return by reference?

The old implementation storeed E instead of unexpected_type<E>, and so we were unable to return get_unexpected() by reference. As we see expected<T,E> as variant<T,unexpected_type<E> we should provide a reference access to unexpected_type<E>.

In addition, if we want to see as a sum type of T and unexpected_type<E>, we would need this access.

Do we want expected<T&, E> in a separated proposal?

Do we really want make_expected_from_call?

Do we want error_or?

As a non-member or member function?

This function should work for all the PossiblyValued types and so could belong to a future PossiblyValued proposal.

Do we want has_error?

As a non-member or member function?

This function should work for all the PossiblyValued types and so could belong to a future PossiblyValued proposal.

Do we want adapt_error?

As a non-member or member function?

This function should work for all the PossiblyValued types and so could belong to a future PossiblyValued proposal.

Proposed Wording

The proposed changes are expressed as edits to N4564 the Working Draft - C++ Extensions for Library Fundamentals V2. The wording has been adapted from the section "Optional objects".

General utilities library

------------------------------------------------------- Insert a new section. -------------------------------------------------------

X.Y Unexpected objects [[unexpected]]

X.Y.1 In general [unexpected.general]

This subclause describes class template unexpected_type that wraps objects intended as unexpected. This wrapped unexpected object is used to be implicitly convertible to other objects.

X.Y.2 Header <experimental/unexpected> synopsis [unexpected.synop]

namespace std {
namespace experimental {
inline namespace fundamentals_v3 {
	// X.Y.3, Unexpected object type
	template <class E>
	  class unexpected_type;
	  
	// X.Y.4, Unexpected factories
	template <class E>
	  constexpr unexpected_type<decay_t<E>> make_unexpected(E&& v);
   
 	// X.Y.5, unexpected_type relational operators
	template <class E>
		constexpr bool 
		operator==(const unexpected_type<E>&, const unexpected_type<E>&);
	template <class E>
		constexpr bool 
		operator!=(const unexpected_type<E>&, const unexpected_type<E>&);
	template <class E>
		constexpr bool 
		operator<(const unexpected_type<E>&, const unexpected_type<E>&);
	template <class E>
		constexpr bool 
		operator>(const unexpected_type<E>&, const unexpected_type<E>&);
	template <class E>
		constexpr bool 
		operator<=(const unexpected_type<E>&, const unexpected_type<E>&);
	template <class E>
		constexpr bool 
		operator>=(const unexpected_type<E>&, const unexpected_type<E>&);
			
}}}

A program that needs the instantiation of template unexpected_type for a reference type or void is ill-formed.

X.Y.3 Unexpected object type [unexpected.object]

template <class E>
class unexpected_type {
public:
    unexpected_type() = delete;
    constexpr explicit unexpected_type(const E&);
    constexpr explicit unexpected_type(E&&);
    constexpr const E& value() const;
    constexpr E & value();
private:
    E val; // exposition only
};

###########################################################################

constexpr explicit unexpected_type(const E&);

Effects: Build an unexpected by copying the parameter to the internal storage val.

###########################################################################

constexpr explicit unexpected_type(E &&);

Effects: Build an unexpected by moving the parameter to the internal storage val.

###########################################################################

constexpr const E& value();
constexpr const E& value() const;

Returns: val.

X.Y.4 Factories [unexpected.factories]

###########################################################################

template <class E>
constexpr unexpected_type<decay_t<E>> make_unexpected(E&& v);

Returns: unexpected_type<decay_t<E>>(v).

X.Y.5 Unexpected Relational operators [unexpected_type.relational_op]

###########################################################################

	template <class E>
		constexpr bool operator==(const unexpected_type<E>& x, const unexpected_type<E>& y);

Requires: E shall meet the requirements of EqualityComparable.

Returns: x.value() == y.value().

Remarks: Specializations of this function template, for which x.value() == y.value() is a core constant expression, shall be constexpr functions.

	template <class E>
		constexpr bool operator!=(const unexpected_type<E>& x, const unexpected_type<E>& y);

Requires: E shall meet the requirements of EqualityComparable.

Returns: x.value() != y.value().

Remarks: Specializations of this function template, for which x.value() != y.value() is a core constant expression, shall be constexpr functions.

	template <class E>
		constexpr bool operator<(const unexpected_type<E>& x, const unexpected_type<E>& y);

Requires: x.value() < y.value().

Returns: x.value() < y.value().

Remarks: Specializations of this function template, for which x.value() < y.value() is a core constant expression, shall be constexpr functions.

	template <class E>
		constexpr bool operator>(const unexpected_type<E>& x, const unexpected_type<E>& y);

Requires: x.value() > y.value().

Returns: x.value() > y.value().

Remarks: Specializations of this function template, for which x.value() > y.value() is a core constant expression, shall be constexpr functions.

	template <class E>
		constexpr bool operator<=(const unexpected_type<E>& x, const unexpected_type<E>& y);

Requires: x.value() <= y.value().

Returns: x.value() <= y.value().

Remarks: Specializations of this function template, for which x.value() <= y.value() is a core constant expression, shall be constexpr functions.

	template <class E>
		constexpr bool operator>=(const unexpected_type<E>& x, const unexpected_type<E>& y);

Returns: !(x < y).

------------------------------------------------------- Insert a new section. -------------------------------------------------------

X.Z Expected objects [[expected]]

X.Z.1 In general [expected.general]

This sub-clause describes class template expected that represents expected objects. An expected<T,E> object is an object that contains the storage for another object and manages the lifetime of this contained object T, alternatively it could contain the storage for another unexpected object E. The contained object may not be initialized after the expected object has been initialized, and may not be destroyed before the expected object has been destroyed. The initialization state of the contained object is tracked by the expected object.

X.Z.2 Header <experimental/expected> synopsis [expected.synop]

namespace std {
namespace experimental {
inline namespace fundamentals_v3 {
	// X.Z.4, expected for object types
	template <class T, class E= error_condition>
		class expected;
	
	// X.Z.5, Specialization for void.
	template <class E>
		class expected<void, E>;
	
	// X.Z.6, unexpect tag
	struct unexpect_t{
	   unexpect_t() = delete;
	};
	constexpr unexpect_t unexpect{'implementation defined'};
	
	// X.Z.7, class bad_expected_access
	class bad_expected_access;
	
	// X.Z.8, Expected relational operators
	template <class T, class E>
		constexpr bool operator==(const expected<T,E>&, const expected<T,E>&);
	template <class T, class E>
		constexpr bool operator!=(const expected<T,E>&, const expected<T,E>&);
	template <class T, class E>
		constexpr bool operator<(const expected<T,E>&, const expected<T,E>&);
	template <class T, class E>
		constexpr bool operator>(const expected<T,E>&, const expected<T,E>&);
	template <class T, class E>
		constexpr bool operator<=(const expected<T,E>&, const expected<T,E>&);
	template <class T, class E>
		constexpr bool operator>=(const expected<T,E>&, const expected<T,E>&);
	
	// X.Z.9, Comparison with T
	template <class T, class E> 
	  constexpr bool operator==(const expected<T,E>&, const T&);
	template <class T, class E> 
	  constexpr bool operator==(const T&, const expected<T,E>&);
	template <class T, class E> 
	  constexpr bool operator!=(const expected<T,E>&, const T&);
	template <class T, class E> 
	  constexpr bool operator!=(const T&, const expected<T,E>&);
	template <class T, class E> 
	  constexpr bool operator<(const expected<T,E>&, const T&);
	template <class T, class E> 
	  constexpr bool operator<(const T&, const expected<T,E>&);
	template <class T, class E> 
	  constexpr bool operator<=(const expected<T,E>&, const T&);
	template <class T, class E> 
	  constexpr bool operator<=(const T&, const expected<T,E>&);
	template <class T, class E> 
	  constexpr bool operator>(const expected<T,E>&, const T&);
	template <class T, class E> 
	  constexpr bool operator>(const T&, const expected<T,E>&);
	template <class T, class E> 
	  constexpr bool operator>=(const expected<T,E>&, const T&);
	template <class T, class E> 
	  constexpr bool operator>=(const T&, const expected<T,E>&);

	// X.Z.10, Comparison with unexpected_type<E>
	template <class T, class E> 
	  constexpr bool operator==(const expected<T,E>&, const unexpected_type<E>&);
	template <class T, class E> 
	  constexpr bool operator==(const unexpected_type<E>&, const expected<T,E>&);
	template <class T, class E> 
	  constexpr bool operator!=(const expected<T,E>&, const unexpected_type<E>&);
	template <class T, class E> 
	  constexpr bool operator!=(const unexpected_type<E>&, const expected<T,E>&);
	template <class T, class E> 
	  constexpr bool operator<(const expected<T,E>&, const unexpected_type<E>&);
	template <class T, class E> 
	  constexpr bool operator<(const unexpected_type<E>&, const expected<T,E>&);
	template <class T, class E> 
	  constexpr bool operator<=(const expected<T,E>&, const unexpected_type<E>&);
	template <class T, class E> 
	  constexpr bool operator<=(const unexpected_type<E>&, const expected<T,E>&);
	template <class T, class E> 
	  constexpr bool operator>(const expected<T,E>&, const unexpected_type<E>&);
	template <class T, class E> 
	  constexpr bool operator>(const unexpected_type<E>&, const expected<T,E>&);
	template <class T, class E> 
	  constexpr bool operator>=(const expected<T,E>&, const unexpected_type<E>&);
	template <class T, class E> 
	  constexpr bool operator>=(const unexpected_type<E>&, const expected<T,E>&);

	// X.Z.11, Specialized algorithms
	void swap(expected<T,E>&, expected<T,E>&) noexcept(see below);

	// X.Z.12, Factories
	template <class T> constexpr expected<decay_t<T>> make_expected(T&& v);
	template <class T, class U> constexpr expected<T> make_expected(U&& v);
	expected<void> make_expected();
	template <class T, class E>
		constexpr expected<T, decay_t<E>> make_expected_from_error(E&& e);
	template <class T, class E, class U>
		constexpr expected<T, E> make_expected_from_error(U&& u);
	template <class F>
		constexpr expected<typename result_type<F>::type>
	make_expected_from_call(F f);
	
	// X.Z.13, hash support
	template <class T, class E> struct hash<expected<T,E>>;
	template <class E> struct hash<expected<void,E>>;
}}}

A program that necessitates the instantiation of template expected<T,E> with T for a reference type or for possibly cv-qualified types in_place_t, unexpect_t or unexpected_type<E> is ill-formed.

X.Z.3 Definitions [expected.defs]

An instance of expected<T,E> is said to be valued if it contains a value of type T. An instance of expected<T,E> is said to be unexpected if it contains an object of type E.

X.Y.4 expected for object types [expected.object]

template <class T, class E>
class expected
{
public:
	typedef T value_type;
	typedef E error_type;
	typedef unexpected_type<E> unexpected_t;

	template <class U>
  	  struct rebind {
		using type = expected<U, error_type>;
	  };

	// X.Z.4.1, constructors
	constexpr expected() noexcept(see below);
	expected(const expected&);
	expected(expected&&) noexcept(see below);
	template <class U, class G>
	EXPLICIT expected(const expected<U,G>&);
	template <class U, class G>
	EXPLICIT expected(expected<U,G>&&) noexcept(see below);
	
	constexpr expected(const T&);
	constexpr expected(T&&);
	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&&...);
	constexpr expected(unexpected_type<E> const&);
	template <class Err>
		constexpr expected(unexpected_type<Err> const&);
	template <class... Args>
		constexpr explicit expected(unexpect_t, Args&&...);
	template <class U, class... Args>
		constexpr explicit expected(unexpect_t, initializer_list<U>, Args&&...);

	// X.Z.4.2, destructor
	~expected();

	// X.Z.4.3, assignment
	expected& operator=(const expected&);
	expected& operator=(expected&&) noexcept(see below);
	template <class U> 
	   expected& operator=(U&&);
	expected& operator=(const unexpected_type<E>&);
	expected& operator=(unexpected_type<E>&&) noexcept(see below);
	
	template <class... Args>
		void emplace(Args&&...);
	template <class U, class... Args>
		void emplace(initializer_list<U>, Args&&...);

	// X.Z.4.4, swap
	void swap(expected&) noexcept(see below);

	// X.Z.4.5, 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() &&;
	constexpr unexpected_type<E> const& get_unexpected() const&;
	constexpr unexpected_type<E> & get_unexpected() &;
	constexpr unexpected_type<E> const&& get_unexpected() const&&;
	constexpr unexpected_type<E> && get_unexpected() &&;
	template <class U> 
		constexpr T value_or(U&&) const&;
	template <class U> 
		T value_or(U&&) &&;

private:
	bool has_val; // exposition only
	union
	{
		value_type val; // exposition only
		unexpected_t unexpect; // exposition only
	};
};

Valued instances of expected<T,E> where T and E is of object type shall contain a value of type T or a value of type E within its own storage. This value is referred to as the contained or the unexpected value of the expected object. Implementations are not permitted to use additional storage, such as dynamic memory, to allocate its contained or unexpected value. The contained or unexpected value shall be allocated in a region of the expected<T,E> storage suitably aligned for the type T and unexpected_type<E>. Members has_val, val and unexpect are provided for exposition only. Implementations need not provide those members. has_val indicates whether the expected object’s contained value has been initialized (and not yet destroyed); when has_val is true val points to the contained value, and when it is false unexpect points to the erroneous value.

T and E shall be object type and shall satisfy the requirements of Destructible.

X.Z.4.1 Constructors [expected.object.ctor]

###########################################################################

constexpr expected() noexcept(’see below’);

Effects: Initializes the contained value as if direct-non-list-initializing an object of type T with the expression T{}.

Postconditions: bool(*this).

Throws: Any exception thrown by the default constructor of T.

Remarks: This constructor shall be constexpr if and only if the value-initialization of T would satisfy the requirements for a constexpr function. The expression inside noexcept is equivalent to: is_nothrow_default_constructible<T>::value. This constructor shall be defined as deleted unless is_default_constructible<T>::value.

###########################################################################

expected(const expected& rhs) noexcept('see below');

Effects: If bool(rhs) initializes the contained value as if direct-non-list-initializing an object of type T with the expression *rhs.

If !bool(rhs) initializes the contained value as if direct-non-list-initializing an object of type unexpected_type<E> with the expression rhs.get_unexpected().

Postconditions: bool(rhs) == bool(*this).

Throws: Any exception thrown by the selected constructor of T orunexpected_type<E>.

Remarks: The expression inside noexcept is equivalent to: is_nothrow_copy_constructible<T>::value and is_nothrow_copy_constructible<E>::value. This constructor shall not participate in overload resolution unless is_copy_constructible<T>::value and is_copy_constructible<E>::value.

###########################################################################

expected(expected && rhs) noexcept(’see below’);

Effects: If bool(rhs) initializes the contained value as if direct-non-list-initializing an object of type T with the expression std::move(*rhs).

If !bool(rhs) initializes the contained value as if direct-non-list-initializing an object of type unexpected_type<E> with the expression std::move(rhs.get_unexpected()).

Postconditions: bool(rhs) == bool(*this) and bool(rhs) is unchanged.

Throws: Any exception thrown by the selected constructor of T or unexpected_type<E>

Remarks: The expression inside noexcept is equivalent to: is_nothrow_move_constructible<T>::value and is_nothrow_move_constructible<E>::value. This constructor shall not participate in overload resolution unless is_move_constructible<T>::value and is_move_constructible<E>::value.

###########################################################################

	template <class U, class G>
	EXPLICIT expected(const expected<U,G>& rhs) noexcept(’see below’);

Effects: If bool(rhs) initializes the contained value as if direct-non-list-initializing an object of type T with the expression *rhs.

If !bool(rhs) initializes the contained value as if direct-non-list-initializing an object of type unexpected_type<E> with the expression rhs.get_unexpected().

Postconditions: bool(rhs) == bool(*this).

Throws: Any exception thrown by the selected constructor of T or unexpected_type<E>

Remarks: The expression inside noexcept is equivalent to: is_nothrow_constructible<T, U&>::value and is_nothrow_constructible<E, G&>::value. This constructor shall not participate in overload resolution unless is_constructible<T, U&>::value and is_constructible<E, G&>::value. The constructor is explicit if and only if is_convertible_v<U const&, T> is false or `is_convertible_v<G const&, E> is false.

###########################################################################

	template <class U, class G>
	EXPLICIT expected(expected<U,G>&& rhs) noexcept(see below);

Effects: If bool(rhs) initializes the contained value as if direct-non-list-initializing an object of type T with the expression std::move(*rhs).

If !bool(rhs) initializes the contained value as if direct-non-list-initializing an object of type unexpected_type<E> with the expression std::move(rhs.get_unexpected()).

Postconditions: bool(rhs) == bool(*this) and bool(rhs) is unchanged.

Throws: Any exception thrown by the selected constructor of T or unexpected_type<E>

Remarks: The expression inside noexcept is equivalent to: is_nothrow_constructible<T, U&&>::value and is_nothrow_constructible<E, G&&>::value. This constructor shall not participate in overload resolution unless is_constructible<T, U&&>::value and is_constructible<E, G&&>::value. The constructor is explicit if and only if is_convertible_v<U&&, T> is false or `is_convertible_v<G&&, E> is false.

###########################################################################

constexpr expected(const T& v) noexcept('see below');

Effects: Initializes the contained value as if direct-non-list-initializing an object of type T with the expression v.

Postconditions: bool(*this).

Throws: Any exception thrown by the selected constructor of T.

Remarks: If T’s selected constructor is a constexpr constructor, this constructor shall be a constexpr constructor. The expression inside noexcept is equivalent to: is_nothrow_constructible<T, U&&>::value. This constructor shall not participate in overload resolution unless is_copy_constructible<T>::value.

###########################################################################

constexpr expected(T&& v) noexcept('see below');

Effects: Initializes the contained value as if direct-non-list-initializing an object of type T with the expression std::move(v).

Postconditions: bool(*this).

Throws: Any exception thrown by the selected constructor of T.

Remarks: If T’s selected constructor is a constexpr constructor, this constructor shall be a constexpr constructor. The expression inside noexcept is equivalent to: is_nothrow_move_constructible<T, U&&>::value. This constructor shall not participate in overload resolution unless is_move_constructible<T>::value.

###########################################################################

template <class... Args>
  constexpr explicit expected(in_place_t, Args&&... args);

Effects: Initializes the contained value as if direct-non-list-initializing an object of type T with the arguments std::forward<Args>(args)....

Postconditions: bool(*this).

Throws: Any exception thrown by the selected constructor of T.

Remarks: If T’s constructor selected for the initialization is a constexpr constructor, this constructor shall be a constexpr constructor. This constructor shall not participate in overload resolution unless is_constructible<T, Args&&...>::value.

###########################################################################

template <class U, class... Args>
  constexpr explicit expected(in_place_t, initializer_list<U> il, Args&&... args);

Effects: Initializes the contained value as if direct-non-list-initializing an object of type T with the arguments il, std::forward<Args>(args)....

Postconditions: bool(*this).

Throws: Any exception thrown by the selected constructor of T.

Remarks: If T’s constructor selected for the initialization is a constexpr constructor, this constructor shall be a constexpr constructor. This constructor shall not participate in overload resolution unless is_constructible<T, initializer_list<U>&, Args&&...>::value.

###########################################################################

constexpr expected(unexpected_type<E> const& e) noexcept('see below');

Effects: Initializes the unexpected value as if direct-non-list-initializing an object of type unexpected_type<E> with the expression e.

Postconditions: ! bool(*this).

Throws: Any exception thrown by the selected constructor of unexpected_type<E>

Remark: If unexpected_type<E>’s selected constructor is a constexpr constructor, this constructor shall be a constexpr constructor. The expression inside noexcept is equivalent to: is_nothrow_copy_constructible<E>::value. This constructor shall not participate in overload resolution unless is_copy_constructible<E>::value.

###########################################################################

constexpr expected(unexpected_type<E>&& e);

Effects: Initializes the unexpected value as if direct-non-list-initializing an object of type unexpected_type<E> with the expression std::move(e).

Postconditions: ! bool(*this).

Throws: Any exception thrown by the selected constructor of unexpected_type<E>

Remark: If unexpected_type’s selected constructor is a constexpr constructor, this constructor shall be a constexpr constructor. The expression inside noexcept is equivalent to: is_nothrow_move_constructible<E>::value. This constructor shall not participate in overload resolution unless is_move_constructible<E>::value.

###########################################################################

template <class... Args>
  constexpr explicit expected(unexpect_t, Args&&... args);

Effects: Initializes the unexpected value as if direct-non-list-initializing an object of typeunexpected_type<E>with the arguments std::forward<Args>(args)....

Postconditions: ! bool(*this).

Throws: Any exception thrown by the selected constructor of unexpected_type<E>

Remarks: If unexpected_type’s constructor selected for the initialization is a constexpr constructor, this constructor shall be a constexpr constructor. This constructor shall not participate in overload resolution unless is_constructible<E, Args&&...>::value.

###########################################################################

template <class U, class... Args>
  constexpr explicit expected(unexpect_t, initializer_list<U> il, Args&&... args);

Effects: Initializes the unexpected value as if direct-non-list-initializing an object of typeunexpected_type<E>with the arguments il, std::forward<Args>(args)....

Postconditions: ! bool(*this).

Throws: Any exception thrown by the selected constructor of unexpected_type<E>.

Remarks: If unexpected_type<E>’s constructor selected for the initialization is a constexpr constructor, this constructor shall be a constexpr constructor. This constructor shall not participate in overload resolution unless is_constructible<E, initializer_list<U>&, Args&&...>::value.

X.Z.4.2 Destructor [expected.object.dtor]

###########################################################################

~expected();

Effects: If is_trivially_destructible<T>::value != true and bool(*this), calls val->T::~T(). If is_trivially_destructible<E>::value != true and ! (bool(*this), calls unexpect->unexpected_type<E>::~unexpected_type<E>().

Remarks: If is_trivially_destructible<T>::value and is_trivially_destructible<E>::value then this destructor shall be a trivial destructor.

X.2.4.3 Assignment [expected.object.assign]

###########################################################################

expected<T,E>& operator=(const expected<T,E>& rhs) noexcept(see below);

Effects:

If bool(*this) and bool(rhs), assigns *rhs to the contained value val;

otherwise, if ! bool(*this) and ! bool(rhs), assigns rhs.get_unexpected() to the contained value unexpect;

otherwise, if bool(*this) and ! bool(rhs),

  • destroys the contained value by calling val->T::~T(),
  • initializes the contained value as if direct-non-list-initializing an object of type unexpected_type<E> with rhs.get_unexpected();

otherwise ! (bool(*this) and bool(rhs)

if is_nothrow_copy_constructible<T>::value

  • destroys the contained value by calling unexpect->unexpected_type<E>::~unexpected_type<E>()
  • initializes the contained value as if direct-non-list-initializing an object of type T with *rhs;

otherwise, if is_nothrow_move_constructible<T>::value

  • constructs a new T tmp on the stack from *rhs,
  • destroys the contained value by calling unexpect->unexpected_type<E>::~unexpected_type<E>(),
  • initializes the contained value as if direct-non-list-initializing an object of type T with tmp;

otherwise as is_nothrow_move_constructible<E>::value

  • move constructs a new unexpected_type<E> tmp on the stack from this.get_unexpecte() (which can't throw as E is nothrow-move-constructible),

  • destroys the contained value by calling unexpect->unexpected_type<E>::~ unexpected_type<E>(),

  • initializes the contained value as if direct-non-list-initializing an object of type T with *rhs. Either,

    • the constructor didn't throw, so mark the expected as holding a T (which can't throw), or
    • the constructor did throw, so move-construct the unexpected_type<E> from the stack tmp back into the expected storage (which can't throw as E` is nothrow-move-constructible), and rethrow the exception.

Returns: *this.

Postconditions: bool(rhs) == bool(*this).

Exception Safety: If any exception is thrown, the values of bool(*this) and bool(rhs) remain unchanged.

If an exception is thrown during the call to T’s copy constructor, no effect.

If an exception is thrown during the call to T’s copy assignment, the state of its contained value is as defined by the exception safety guarantee of T’s copy assignment.

Remarks: This signature shall not participate in overload resolution unless ` is_copy_assignable::value and is_copy_assignable::value and is_copy_constructible::value and is_copy_constructible::value and is_nothrow_move_constructible::value .

###########################################################################

expected<T,E>& operator=(expected<T,E>&& rhs) noexcept(/*see below*/);

Effects:

If bool(*this) and bool(rhs), move assign *rhs to the contained value val;

otherwise, if ! bool(*this) and ! bool(rhs), move assign rhs.get_unexpected() to the contained value unexpect;

otherwise, if bool(*this) and ! bool(rhs),

  • destroys the contained value by calling val->T::~T(),
  • initializes the contained value as if direct-non-list-initializing an object of type unexpected_type<E> with move(forward<expected<T,E>>(rhs).get_unexpected());

otherwise ! (bool(*this) and bool(rhs)

if is_nothrow_move_constructible<T>::value

  • destroys the contained value by calling unexpect->unexpected_type<E>::~unexpected_type<E>(),
  • initializes the contained value as if direct-non-list-initializing an object of type T with *move(rhs);

otherwise as is_nothrow_move_constructible<E>::value

  • move constructs a new unpepected_type<E> tmp on the stack from this.get_unexpected() (which can't throw as E is nothrow-move-constructible),

  • destroys the contained value by calling unexpect->unexpected_type<E>::~unexpected_type<E>(),

  • initializes the contained value as if direct-non-list-initializing an object of type T with *move(rhs). Either,

    • The constructor didn't throw, so mark the expected as holding a T (which can't throw), or
    • The constructor did throw, so move-construct the unexpected_type<E> from the stack tmp back into the expected storage (which can't throw as E is nothrow-move-constructible), and rethrow the exception.

Returns: *this.

Postconditions: bool(rhs) == bool(*this).

Remarks: The expression inside noexcept is equivalent to: is_nothrow_move_assignable<T>::value && is_nothrow_move_constructible<T>::value.

Exception Safety: If any exception is thrown, the values of bool(*this) and bool(rhs) remain unchanged.

If an exception is thrown during the call to T’s copy constructor, no effect.

If an exception is thrown during the call to T’s copy assignment, the state of its contained value is as defined by the exception safety guarantee of T’s copy assignment.

If an exception is thrown during the call to E’s copy assignment, the state of its contained unexpected value is as defined by the exception safety guarantee of E’s copy assignment.

Remarks: This signature shall not participate in overload resolution unless is_move_constructible<T>::value and is_move_assignable<T>::value and is_nothrow_move_constructible<E>::value and is_nothrow_move_assignable<E>::value.

###########################################################################

template <class U>
  expected<T,E>& operator=(U&& v);

Effects:

If bool(*this), assigns forward<U>(v) to the contained value val;

otherwise, if is_nothrow_constructible<T, U&&>::value

  • destroys the contained value by calling unexpect->unexpected_type<E>::~unexpected_type<E>(),
  • initializes the contained value as if direct-non-list-initializing an object of type T with forward<U>(v) and
  • set has_valueto true;

otherwise as is_nothrow_move_constructible<E>::value

  • move constructs a new unexpected_type<E> tmp on the stack from this.get_unexpected() (which can't throw as E is nothrow-move-constructible),

  • destroys the contained value by calling unexpect->unexpected_type<E>::~unexpected_type<E>(),

  • initializes the contained value as if direct-non-list-initializing an object of type T with forward<U>(v). Either,

    • the constructor didn't throw, so mark the expected as holding a T (which can't throw), that is set has_val to true, or
    • the constructor did throw, so move construct the unexpected_type<E> from the stack tmp back into the expected storage (which can't throw as E is nothrow-move-constructible), and re-throw the exception.

Returns: *this.

Postconditions: bool(*this).

Exception Safety: If any exception is thrown, the value of bool(*this) remains unchanged.

If an exception is thrown during the call to T’s constructor, no effect.

If an exception is thrown during the call to T’s copy assignment, the state of its contained value is as defined by the exception safety guarantee of T’s copy assignment.

Remarks:

This signature shall not participate in overload resolution unless is_same<T,U>::value and and is_nothrow_move_constructible<E>::value.

[Note: The reason to provide such generic assignment and then constraining it so that effectively T == U is to guarantee that assignment of the form o = {} is unambiguous. —end note]

###########################################################################

expected<T,E>& operator=(unexpected_type<E> const& e) noexcept(`see below`);

Effects:

If ! bool(*this), assignsrhs.get_unexpected()to the contained valueunexpect`;

otherwise,

  • destroys the contained value by calling val->T::~T(),
  • initializes the contained value as if direct-non-list-initializing an object of type unexpected_type<E> with forward<expected<T,E>>(rhs).get_unexpected() and set has_val to false.

Returns: *this.

Postconditions: ! bool(*this).

Exception Safety: If any exception is thrown, value of valued remains unchanged.

Remarks: This signature shall not participate in overload resolution unless is_nothrow_copy_constructible<E>::value and is_assignable<E&, E>::value.

###########################################################################

expected<T,E>& operator=(unexpected_type<E> && e);

Effects:

If ! bool(*this), move assignrhs.get_unexpected()to the contained valueunexpect`;

otherwise,

  • destroys the contained value by calling val->T::~T(),
  • initializes the contained value as if direct-non-list-initializing an object of type unexpected_type<E> with move(forward<expected<T,E>>(rhs).get_unexpected()) and set has_val to false.

Returns: *this.

Postconditions: ! bool(*this).

Exception Safety: If any exception is thrown, value of valued remains unchanged.

Remarks: This signature shall not participate in overload resolution unless is_nothrow_move_constructible<E>::value and is_move_assignable<E&, E>::value.

###########################################################################

template <class... Args>
  void emplace(Args&&... args);

Effects:

If bool(*this), assigns forward<U>(v) to the contained value val as if constructing an object of type T with the arguments std::forward<Args>(args)...

otherwise, if is_nothrow_constructible<T, Args&&...>::value

  • destroys the contained value by calling unexpect->unexpected_type<E>::~unexpected_type<E>(),
  • initializes the contained value as if direct-non-list-initializing an object of type T with std::forward<Args>(args)... and
  • set has_valueto true;

otherwise as is_nothrow_constructible<T, U&&>::value

  • move constructs a new unexpected_type<E> tmp on the stack from this.get_unexpected() (which can't throw as E is nothrow-move-constructible),

  • destroys the contained value by calling unexpect->unexpected_type<E>::~unexpected_type<E>(),

  • initializes the contained value as if direct-non-list-initializing an object of type T with forward<U>(v). Either,

    • the constructor didn't throw, so mark the expected as holding a T (which can't throw), that is set has_valueto true, or
    • the constructor did throw, so move-construct the unexpected_type<E> from the stack tmp back into the expected storage (which can't throw as E is nothrow-move-constructible), and rethrow the exception.

if bool(*this), assigns the contained value val as if constructing an object of type T with the arguments std::forward<Args>(args)...; otherwise, destroys the contained value by calling unexpect->unexpected_type<E>::~unexpected_type<E>() and initializes the contained value as if constructing an object of type T with the arguments std::forward<Args>(args)....

Postconditions: bool(*this).

Exception Safety: If an exception is thrown during the call to T’s assignment, nothing changes.

Throws: Any exception thrown by the selected assignment of T.

Remarks: This signature shall not participate in overload resolution unless is_no_throw_constructible<T, Args&&...>::value.

###########################################################################

template <class U, class... Args>
  void emplace(initializer_list<U> il, Args&&... args);

Effects: if bool(*this), assigns the contained value val as if constructing an object of type T with the arguments il, std::forward<Args>(args)..., otherwise destroys the contained value by calling unexpect->unexpected_type<E>::~unexpected_type<E>() and initializes the contained value as if constructing an object of type T with the arguments il, std::forward<Args>(args)....

Postconditions: bool(*this).

Exception Safety: If an exception is thrown during the call to T’s assignment nothing changes.

Throws: Any exception thrown by the selected assignment of T.

Remarks: The function shall not participate in overload resolution unless: is_no_throw_constructible<T, initializer_list<U>&, Args&&...>::value.

X.Z.4.4 Swap [expected.object.swap]

###########################################################################

void swap(expected<T,E>& rhs) noexcept(/*see below*/);

Effects: if bool(*this) and bool(rhs), calls swap(val, rhs.val), otherwise if ! bool(*this) and ! bool(rhs), calls swap(err, rhs.err), otherwise exchanges values of rhs and *this.

Exception Safety: TBC

Throws: Any exceptions that the expressions in the Effects clause throw.

Remarks: The expression inside noexcept is equivalent to: is_nothrow_move_constructible<T>::value and noexcept(swap(declval<T&>(), declval<T&>())) and is_nothrow_move_constructible<E>::value and noexcept(swap(declval<E&>(), declval<E&>())). The function shall not participate in overload resolution unless: LValues of type T shall be Swappable, is_move_constructible<T>::value, LValues of type E shall be Swappable and is_move_constructible<T>::value.

X.2.4.5 Observers [expected.object.observe]

###########################################################################

constexpr const T* operator->() const;
T* operator->();

Requires: bool(*this).

Returns: &val.

Remarks: Unless T is a user-defined type with overloaded unary operator&, the first function shall be a constexpr function.

###########################################################################

constexpr const T& operator *() const&;
T& operator *() &;

Requires: bool(*this).

Returns: val.

Remarks: The first function shall be a constexpr function.

###########################################################################

constexpr T&& operator *() &&;
constexpr const T&& operator *() const&&;

Requires: bool(*this).

Returns: move(val).

Remarks: This function shall be a constexpr function.

###########################################################################

constexpr explicit operator bool() noexcept;

Returns: has_val.

Remarks: This function shall be a constexpr function.

###########################################################################

constexpr const T& value() const&;
constexpr T& value() &;

Returns: val, if bool(*this).

Throws:

• Otherwise bad_expected_access(err) if ! bool(*this).

Remarks: The first and third functions shall be constexpr functions. ###########################################################################

constexpr T&& value() &&;
constexpr const T&& value() const&&;

Returns: move(val), if bool(*this).

Throws:

• Otherwise bad_expected_access(err) if ! bool(*this).

Remarks: The first and third functions shall be constexpr functions.

###########################################################################

constexpr const E& error() const&;
constexpr E& error() &;

Requires: ! bool(*this).

Returns: unexpect.value().

Remarks: The first function shall be a constexpr function.

###########################################################################

constexpr E&& error() &&;
constexpr const E&& error() const &&;

Requires: ! bool(*this).

Returns: move(unexpect.value()).

Remarks: The first function shall be a constexpr function.

###########################################################################

	constexpr unexpected_type<E> const& get_unexpected() const&;
	constexpr unexpected_type<E> & get_unexpected() &;

Requires: ! bool(*this).

Returns: unexpect.

###########################################################################

	constexpr unexpected_type<E> const&& get_unexpected() const&&;
	constexpr unexpected_type<E> && get_unexpected() &&;

Requires: ! bool(*this).

Returns: move(unexpect).

###########################################################################

template <class U>
  constexpr T value_or(U&& v) const&;

Returns: bool(*this) ? **this : static_cast<T>(std::forward<U>(v)).

Exception Safety: If has_val and exception is thrown during the call to T’s constructor, the value of has_val and v remains unchanged and the state of val is determined by the exception safety guarantee of the selected constructor of T. Otherwise, when exception is thrown during the call to T’s constructor, the value of *this remains unchanged and the state of v is determined by the exception safety guarantee of the selected constructor of T.

Throws: Any exception thrown by the selected constructor of T.

Remarks: If both constructors of T which could be selected are constexpr constructors, this function shall be a constexpr function.

Remarks: The function shall not participate in overload resolution unless: is_copy_constructible<T>::value and is_convertible<U&&, T>::value.

###########################################################################

template <class U>
  T value_or(U&& v) &&;

Returns: bool(*this) ? std::move(**this) : static_cast<T>(std::forward<U>(v)).

Exception Safety: If has_val and exception is thrown during the call to T’s constructor, the value of has_val and v remains unchanged and the state of val is determined by the exception safety guarantee of the T’s constructor.

Otherwise, when exception is thrown during the call to T’s constructor, the value of *this remains unchanged and the state of v is determined by the exception safety guarantee of the selected constructor of T.

Throws: Any exception thrown by the selected constructor of T.

Remarks: The function shall not participate in overload resolution unless: is_move_constructible<T>::value and is_convertible<U&&, T>::value.

X.Z.6 expected for void [expected.object.void]

template <class E>
class expected<void, E>
{
public:
	typedef void value_type;
	typedef E error_type;
	typedef unexpected_type<E> unexpected_t;
	template <class U>
	  struct rebind {
		typedef expected<U, error_type> type;
	  };
	// ??, constructors
	constexpr expected() noexcept;
	expected(const expected&);
	expected(expected&&) noexcept(see below);
	template <class G>
	EXPLICIT expected(const expected<void,G>&);
	template <class G>
	EXPLICIT expected(expected<void,G>&&) noexcept(see below);
	
	constexpr explicit expected(in_place_t);
	constexpr expected(unexpected_type<E> const&);
	template <class Err>
	  constexpr expected(unexpected_type<Err> const&);
	// ??, destructor
	~expected();
	// ??, assignment
	expected& operator=(const expected&);
	expected& operator=(expected&&) noexcept(see below);
	void emplace();
	// ??, swap
	void swap(expected&) noexcept(see below);
	// ??, observers
	constexpr explicit operator bool() const noexcept;
	constexpr bool has_value() const noexcept;
	void value() const;
	constexpr const E& error() const&;
	constexpr E& error() &;
	constexpr const E && error() const &&;
	constexpr E&& error() &&;
	constexpr unexpected_type<E> const& get_unexpected() const&;
	constexpr unexpected_type<E> & get_unexpected() &;
	constexpr unexpected_type<E> const&& get_unexpected() const&&;
	constexpr unexpected_type<E> && get_unexpected() &&;
  
private:
	bool has_val; // exposition only
	union
	{
		unsigned char dummy; // exposition only
		unexpected_t unexpect; // exposition only
	};
};

TODO: Describe the functions

X.Z.7 unexpect tag [expected.unexpect]

struct unexpect_t;
constexpr unexpect_t unexpect;

X.Z.8 Template Class bad_expected_access [expected.bad_expected_access]

template <class E>
class bad_expected_access : public exception {
public:
	explicit bad_expected_access(E);
   virtual const char* what() const noexcept overrride;	
	const E& error() const&;
	E& error() &;
	E&& error() &&;
};

The template class bad_expected_access defines the type of objects thrown as exceptions to report the situation where an attempt is made to access the value of a unexpected expected object.

###########################################################################

bad_expected_access::bad_expected_access(E e);

Effects: Constructs an object of class bad_expected_access storing the parameter.

###########################################################################

	const E& error() const&;
	E& error() &;
	E&& error() &&;

Returns: The stored error.

Remarks: The first function shall be a constexpr function.

###########################################################################

virtual const char* what() const noexcept overrride;

Returns: An implementation-defined NTBS.

X.Z.8 Expected Relational operators [expected.relational_op]

###########################################################################

	template <class T, class E>
		constexpr bool operator==(const expected<T,E>& x, const expected<T,E>& y);

Requires: Tand unexpected_type<E> shall meet the requirements of EqualityComparable.

Returns: If bool(x) != bool(y), false; otherwise if bool(x) == false, x.get_unexpected() == y.get_unexpected(); otherwise *x == *y.

Remarks: Specializations of this function template, for which *x == *y and x.get_unexpected() == y.get_unexpected() are core constant expression, shall be constexpr functions.

###########################################################################

	template <class T, class E>
		constexpr bool operator!=(const expected<T,E>& x, const expected<T,E>& y);

Requires: Tand unexpected_type<E> shall meet the requirements of EqualityComparable.

Returns: If bool(x) != bool(y), true; otherwise if bool(x) == false, x.get_unexpected() != y.get_unexpected(); otherwise *x != *y.

Remarks: Specializations of this function template, for which *x != *y and x.get_unexpected() != y.get_unexpected() are core constant expression, shall be constexpr functions.

###########################################################################

	template <class T, class E>
		constexpr bool operator<(const expected<T,E>& x, const expected<T,E>& y);

Requires: *x < *y and x.get_unexpected() < y.get_unexpected() shall be well-formed and its result shall be convertible to bool.

Returns: If !x && y, false; otherwise, if x && &y, true; otherwise, if &x && &y, x.get_unexpected() < y.get_unexpected(); otherwise *x < *y.

Remarks: Specializations of this function template, for which *x < *y and x.get_unexpected() < y.get_unexpected() are core constant expression, shall be constexpr functions.

###########################################################################

	template <class T, class E>
		constexpr bool operator>(const expected<T,E>& x, const expected<T,E>& y);

Requires: *x > *y and x.get_unexpected() > y.get_unexpected() shall be well-formed and its result shall be convertible to bool.

Returns: If x && &y, true; otherwise, if !x && y, false; otherwise, if &x && &y, x.get_unexpected() > y.get_unexpected(); otherwise *x > *y.

Remarks: Specializations of this function template, for which *x > *y and x.get_unexpected() > y.get_unexpected() are core constant expression, shall be constexpr functions.

###########################################################################

	template <class T, class E>
		constexpr bool operator<=(const expected<T,E>& x, const expected<T,E>& y);

Requires: *x <= *y and x.get_unexpected() <= y.get_unexpected() shall be well-formed and its result shall be convertible to bool.

Returns: If !x && y, false; otherwise, if x && &y, true; otherwise, if &x && &y, x.get_unexpected() <= y.get_unexpected(); otherwise *x <= *y.

Remarks: Specializations of this function template, for which *x > *y and x.get_unexpected() > y.get_unexpected() are core constant expression, shall be constexpr functions.

###########################################################################

	template <class T, class E>
		constexpr bool operator>=(const expected<T,E>& x, const expected<T,E>& y);

Requires: *x >= *y and x.get_unexpected() >= y.get_unexpected() shall be well-formed and its result shall be convertible to bool.

Returns: If x && &y, true; otherwise, if !x && y, false; otherwise, if &x && &y, x.get_unexpected() >= y.get_unexpected(); otherwise *x >= *y.

Remarks: Specializations of this function template, for which *x > *y and x.get_unexpected() > y.get_unexpected() are core constant expression, shall be constexpr functions.

X.Z.9 Comparison with T [expected.comparison_T]

###########################################################################

	template <class T, class E> constexpr bool operator==(const expected<T,E>& x, const T& v);
	template <class T, class E> constexpr bool operator==(const T& v, const expected<T,E>& x);

Returns: bool(x) ? *x == v : false.

###########################################################################

	template <class T, class E> constexpr bool operator!=(const expected<T,E>& x, const T& v);
	template <class T, class E> constexpr bool operator!=(const T& v, const expected<T,E>& x);

Returns: bool(x) ? *x != v : false.

###########################################################################

	template <class T, class E> constexpr bool operator<(const expected<T,E>& x, const T& v);

Returns: bool(x) ? *x < v : false.

###########################################################################

	template <class T, class E> constexpr bool operator<(const T& v, const expected<T,E>& x);

Returns: bool(x) ? v < *x : true.

###########################################################################

	template <class T, class E> constexpr bool operator<=(const expected<T,E>& x, const T& v);

Returns: bool(x) ? *x <= v : false.

###########################################################################

	template <class T, class E> constexpr bool operator<=(const T& v, const expected<T,E>& x);

Returns: bool(x) ? v <= *x : true.

###########################################################################

	template <class T, class E> constexpr bool operator>(const expected<T,E>& x, const T& v);

Returns: bool(x) ? *x > v : true.

###########################################################################

	template <class T, class E> constexpr bool operator>(const T& v, const expected<T,E>& x);

Returns: bool(x) ? v > *x : false.

###########################################################################

	template <class T, class E> constexpr bool operator>=(const expected<T,E>& x, const T& v);

Returns: bool(x) ? *x >= v : true.

###########################################################################

	template <class T, class E> constexpr bool operator>=(const T& v, const expected<T,E>& x);

Returns: bool(x) ? v >= *x : false.

X.Z.10 Comparison with unexpected_type<E> [expected.comparison_unexpected_E]

###########################################################################

	template <class T, class E> constexpr bool operator==(const expected<T,E>& x, const unexpected_type<E>& e);
	template <class T, class E> constexpr bool operator==(const unexpected_type<E>& e, const expected<T,E>& x);

Returns: bool(x) ? true ? x.get_unexpected() == e.

###########################################################################

	template <class T, class E> constexpr bool operator!=(const expected<T,E>& x, const unexpected_type<E>& e);
	template <class T, class E> constexpr bool operator!=(const unexpected_type<E>& e, const expected<T,E>& x);

Returns: bool(x) ? false ? x.get_unexpected() != e.

###########################################################################

	template <class T, class E> constexpr bool operator<(const expected<T,E>& x, const unexpected_type<E>& e);

Returns: bool(x) ? true : x.get_unexpected() < e.

###########################################################################

	template <class T, class E> constexpr bool operator<(const unexpected_type<E>& e, const expected<T,E>& x);

Returns: bool(x) ? false : e < x.get_unexpected().

###########################################################################

	template <class T, class E> constexpr bool operator<=(const expected<T,E>& x, const unexpected_type<E>& e);

Returns: bool(x) ? true : x.get_unexpected() <= e.

###########################################################################

	template <class T, class E> constexpr bool operator<=(const unexpected_type<E>& e, const expected<T,E>& y);

Returns: bool(x) ? false : e < x.get_unexpected().

###########################################################################

	template <class T, class E> constexpr bool operator>(const expected<T,E>& x, const unexpected_type<E>& e);

Returns: bool(x) ? false : x.get_unexpected() > e.

###########################################################################

	template <class T, class E> constexpr bool operator>(const unexpected_type<E>& e, const expected<T,E>& x);

Returns: bool(x) ? true : e > x.get_unexpected().

###########################################################################

	template <class T, class E> constexpr bool operator>=(const expected<T,E>& x, const unexpected_type<E>& e);

Returns: bool(x) ? false : x.get_unexpected() >= e.

###########################################################################

	template <class T, class E> constexpr bool operator>=(const unexpected_type<E>& e, const expected<T,E>& x);

Returns: bool(x) ? false : e >= x.get_unexpected().

X.Z.11 Specialized algorithms [expected.specalg]

###########################################################################

template <class T, class E>
void swap(expected<T,E>& x, expected<T,E>& y) noexcept(noexcept(x.swap(y)));

Effects: Equivalent to x.swap(y).

X.Z.12 Expected Factories [expected.factories]

###########################################################################

template <class T>
constexpr expected<typename decay<T>::type> make_expected(T&& v);

Returns: expected<typename decay<T>::type>(std::forward<T>(v)).

###########################################################################

template <class T, class U>
constexpr expected<T> make_expected(U&& v);

Returns: expected<T>(std::forward<U>(v)).

Remark: The function shall not participate in overload resolution unless: is_convertible<U&&, T>::value.

###########################################################################

template <class T, class E>
constexpr expected<T, decay_t<E>> make_expected_from_error(E&& e);

Returns: expected<T, decay_t<E>>(make_unexpected(e));

###########################################################################

template <class T, class E, class U>
constexpr expected<T, E> make_expected_from_error(U&& u);

Returns: expected<T, E>(make_unexpected(E{forward<U>(u)}));

###########################################################################

template <class F>
constexpr typename expected<result_of<F()>::type make_expected_from_call(F funct);

Equivalent to:

try
	return make_expected(funct());
catch (...)
	return make_unexpected_from_current_exception();

X.Z.13 Hash support [expected.hash]

###########################################################################

template <class T, class E>
struct hash<expected<T,E>>;

Requires: The template specializations hash<T> and hash<E> shall meet the requirements of class template hash (Z.X.Y). The template specialization hash<expected<T,E>> shall meet the requirements of class template hash. For an object e of type expected<T,E>, if bool(e), hash<expected<T,E>>()(e) shall evaluate to a combination of the hashing true and hash<T>()(*e); otherwise a combination of hashing false and hash<E>()(e.error()).

###########################################################################

template <class E>
struct hash<expected<void, E>>;

Requires: The template specialization hash<E> shall meet the requirements of class template hash (Z.X.Y). The template specialization hash<expected<void,E>> shall meet the requirements of class template hash. For an object e of type expected<void,E>, if bool(e), hash<expected<void,E>>()(e) shall evaluate to the hashing true; otherwise it evaluates to a combination of hashing false and hash<E>()(e.error()).

X.Z.14 expected as a meta-fuction [expected.object.meta]

###########################################################################

template <class E>
class expected<_t, E> 
{
public:
	template <class T>
	using apply = expected<T,E>;
};

Implementability

This proposal can be implemented as pure library extension, without any compiler magic support, in C++14.

An almost full reference implementation of this proposal can be found at ExpectedImpl. However this implementation requires that both T and E don't throw while constructing and assigning.

Future Work

Allocator support

As optional<T>, expected<T,E> does not allocate memory. So it can do without allocators. However it can be useful in compound types like:

typedef vector<expected<vector<int, MyAlloc>, error>, MyAlloc> MyVec;
MyVec v{ v2, MyAlloc{} };

One could expect that the allocator argument is forwarded in this constructor call to the nested vectors that use the same allocator. Allocator support would enable this. std::tuple and std::variant offers this functionality.

Variadic expected

A typical case could combine expected and variant expected<T,variant<E1, ..., En>>. We could extend expected to take a variadic number of errors expected<T, E1, ..., En> in order to provide an more user friendly interface.

Some possible advantages with expected<T, E...>

  • expected<T> would be valid and could be convertible to any expected<T, E...>

Some possible disadvantages with expected<T, E...>:

  • no possible default E to error_code.
  • get_unexpected cannot return by reference without additional cost elsewhere (size).

expected<T&, E>

Function that return by reference couldn't use expected to transport the error. This means that we could start seen functions that return variant<T&, E> instead.

Acknowledgements

We are very grateful to Andrei Alexandrescu for his talk, which was the origin of this work. We thanks also to every one that has contributed to the Haskell either monad, as either’s interface was a source of inspiration.

Thanks to Fernando Cacciola, Andrzej KrzemieÃĚâĂđski and every one that has contributed to the wording and the rationale of N3793.

Thanks to David Sankel, Mark Calabrese, Axel Naumann and those that participated in the Oulu's review for insisting in the extraction of the monadic interface.

Thanks to Niall Douglas for reporting some possible issues in this proposal and for raising alternative design approaches after implementing expected in its Boost.Outcome library. Thank to Andrzej KrzemieÃĚâĂđski and Peter Dimov for all its pertinent exchanges during this review.

Special thanks and recognition goes to Technical Center of Nokia - Lannion for supporting in part the production of this proposal.

References

https://akrzemi1.wordpress.com/2014/12/02/a-gotcha-with-optional

Appendix I: Alternative designs

A Configurable Expected

Expected might be configurable through a trait expected_traits.

The first variation point is the behavior of value() when expected<T,E> contains an error. The current strategy throw a bad_expected_access exception but it might not be satisfactory for every error type. For example, some might want to encapsulate an error_code into a system_error. Or in debug mode, they might want to use an assert call.

We could as well make the exception thrown depend on the Error overloading a rethrow_on_unexpected.

Which exception throw when the user try to get the expected value but there is none?

It has been suggested to let the user decide the exception that would be throw when the user try to get the expected value but there is none, as third parameter.

While there is no major complexity doing it, as it just needs a third parameter that could default to the appropriated class,

template <class T, class Error, class Exception = bad_expected_access>
struct expected;

The authors consider that this is not really needed and that this parameter should not really be part of the type.

The user could use value_or_throw()

expected<int, std::error_code> f();
expected<int, std::error_code> e = f();
auto i = value_or_throw<std::system_error>(e);

where

template <class Exception, class T, class E>
constexpr const T& value_or_throw(expected<T,E> const& e) 
{
	if (!e.has_value())
	   throw Exception(e.error());
	return *e;
}

A function like this one could be added to the standard, but this proposal doesn’t request it.

An alternative is to overload the value function with the exception to throw.

template <class Exception, class T, class E>
constexpr value_type const& value() const&

About expected<T, ErrorCode, Exception>

It has been suggested also to extend the design into something that contains

  • a T, or
  • an ErrorCode, or
  • an exception_ptr

This is the case of [Outcome] library.

Again there is no major difficulty to implement it, but instead of having one variation point we have two, that is, is there a value, and if not, if is there an exception_ptr.

Better to have a variadic expected<T, E...>

Appendix II: Related types

Variant

expected<T,E> can be seen as a specialization of boost::variant<unexpected_type<E>,T> which gives a specific intent to its first parameter, that is, it represents the type of the expected contained value. This specificity allows to provide a pointer like interface, as it is the case for std::optional<T>. Even if the standard included a class variant<T,E>, the interface provided by expected<T,E> is more specific and closer to what the user could expect as the result type of a function. In addition, expected<T,E> doesn’t intend to be used to define recursive data as boost::variant<> does.

The following table presents a brief comparison between boost::variant<unexpected_type<E>, T> and expected<T,E>.

std::variant<T, unexpected_type<E>> expected<T,E>
never-empty warranty no yes
accepts is_same<T,E> yes yes
swap yes yes
factories no make_expected / make_unexpected
hash yes yes
value_type no yes
default constructor yes (if T is default constructible) yes (if T is default constructible)
observers boost::get<T> and boost::get<E> pointer-like / value / error / value_or
visitation visit no

Optional

We can see expected<T,E> as an std::optional<T> that collapse all the values of E to nullopt.

We can convert an expected<T,E> to an optional<T> with the possible loss of information.

template <class T>
optional<T> make_optional(expected<T,E> v) {
	if (v) return make_optional(*v);
	else nullopt;
}

We can convert an optional<T> to an expected<T,E> without knowledge of the root cause. We consider that E() is equal to nullopt since it shouldn’t bring more informations (however it depends on the underlying error — we considered exception_ptr and error_condition).

template <class T, class E>
expected<T,E> make_expected(optional<T> v) {
	if (v) return make_expected(*v);
	else make_unexpected(E());
}

The problem is if E is a status and E() denotes a success value.

Promise and Future

We can see expected<T> as an always ready future<T>. While promise<>/future<> focuses on inter-thread asynchronous communication, excepted<T, E> focus on eager and synchronous computations. We can move a ready future<T> to an expected<T> with no loss of information.

template <class T>
expected<T, exception_ptr> make_expected(future<T>&& f) {
	assert (f.ready() && "future not ready");
	try {
		return f.get();
	} catch (...) {
		return unexpected_type<exception_ptr>{current_exception()};
	}
}

We could also create a future<T> from an expected<T>

template <class T>
future<T> make_future(expected<T> e) {
	if (e)
		return make_ready_future(*e);
	else
		return make_exceptional_future<T>(e.error());
};

Comparison between optional, expected and future

The following table presents a brief comparison between optional<T>, expected<T,E> and promise<T>/future<T>.

optional expected promise/future
specific null value yes no non
relational operators yes yes no
swap yes yes yes
factories make_optional / nullopt make_expected / make_unexpected make_ready_future / make_exceptional_future
hash yes yes yes
value_type yes yes no
default constructor yes yes (if T is default constructible) yes
allocators no no yes
emplace yes yes no
bool conversion yes yes no
state bool() bool() / valid valid / ready
observers pointer-like / value / value_or pointer-like / value / error / value_or get
visitation no no then
grouping n/a n/a when_all / when_any