This paper revises [P0323r5], which applied feedback obtained from LEWG and EWG.
r6 takes in account the LWG feedback in Jacksonville.
-
Locate
unexpected
inside<expected>
header and section. -
Define friend functions for equality operators and
swap
. -
Follows as much as possible
variant
kind of wording. -
Reword
emplace(initializer_list)
overload. -
Use
trait
istrue
/false
. -
Simplify Effects clause when there is a simple return.
We have added some additional open points that have not been identified previously after the LWG’s feedback:
-
Has
unexpected<void>
a sense? -
Missing
expected<T,E>::emplace
forunexpected<E>
. -
The in-place construction from a
unexpected<E>
using theunexpect_t
tag doesn’t conveys the in-place nature of this constructor.
These should be fixed in another paper if LEWG considered the fix necessary.
[P0323r4] contains motivation, design rationale, implementability information, sample usage, history, alternative designs and related types. r6 and r5 only contain wording and open questions because their purpose is twofold:
-
Present appropriate wording for inclusion in the Library Fundamentals TS v3.
-
List open questions which the TS should aim to answer.
1. Wording
Below, substitute the �
character with a number or name the editor finds
appropriate for the sub-section.
1.1. �.� Expected objects [expected]
1.2. �.�.1 In general [expected.general]
This subclause describes class template expected
that represents expected
objects. An expected<T, E>
object holds an object of type T
or an object of
type unexpected<E>
and manages the lifetime of the contained objects.
1.3. �.�.2 Header <experimental/expected>
synopsis [expected.synop]
namespace std { namespace experimental { inline namespace fundamentals_v3 { // �.�.4, class template expected template <class T, class E> class expected; // �.�.5, class template unexpected template <class E> class unexpected; template <class E> unexpected(E) -> unexpected<E>; // �.�.6, class bad_expected_access template <class E> class bad_expected_access; // �.�.7, Specialization for void template <> class bad_expected_access<void>; // �.�.8, unexpect tag struct unexpect_t { explicit unexpect_t() = default; }; inline constexpr unexpect_t unexpect{}; }}}
A program that instantiates the definition of unexpected
for a
non-object type, a function type, an array object, an unexpected<G>
object type or
an object type cv-qualified is ill-formed.
A program that instantiates the definition of template expected<T, E>
for a reference type or for possibly cv-qualified types in_place_t
, unexpect_t
or unexpected<E>
for the T
parameter or
for a reference type or for possibly cv-qualified void
type for the E
parameter
is ill-formed.
1.4. �.�.3 Definitions [expected.defs]
An instance of expected<T, E>
is said to be valued if it contains a object of
type T
. An instance of expected<T, E>
is said to be disapointed if it
contains an object of type unexpected<E>
.
1.5. �.�.4 Class template expected [expected.expected]
template <class T, class E> class expected { public: using value_type = T; using error_type = E; using unexpected_type = unexpected<E>; template <class U> using rebind = expected<U, error_type>; // �.�.4.1, constructors constexpr expected(); constexpr expected(const expected&); constexpr expected(expected&&) noexcept(see below); template <class U, class G> EXPLICIT constexpr expected(const expected<U, G>&); template <class U, class G> EXPLICIT constexpr expected(expected<U, G>&&); template <class U = T> EXPLICIT constexpr expected(U&& v); template <class G = E> constexpr expected(const unexpected<G>&); template <class G = E> constexpr expected(unexpected<G> &&); template <class... Args> constexpr explicit expected(in_place_t, Args&&...); template <class U, class... Args> constexpr explicit expected(in_place_t, initializer_list<U>, Args&&...); template <class... Args> constexpr explicit expected(unexpect_t, Args&&...); template <class U, class... Args> constexpr explicit expected(unexpect_t, initializer_list<U>, Args&&...); // �.�.4.2, destructor ~expected(); // �.�.4.3, assignment expected& operator=(const expected&); expected& operator=(expected&&) noexcept(see below); template <class U = T> expected& operator=(U&&); template <class G = E> expected& operator=(const unexpected<G>&); template <class G = E> expected& operator=(unexpected<G>&&); // �.�.4.4, modifiers template <class... Args> T& emplace(Args&&...); template <class U, class... Args> T& emplace(initializer_list<U>, Args&&...); // �.�.4.5, swap void swap(expected&) noexcept(see below); // �.�.4.6, observers constexpr const T* operator ->() const; constexpr T* operator ->(); constexpr const T& operator *() const&; constexpr T& operator *() &; constexpr const T&& operator *() const &&; constexpr T&& operator *() &&; constexpr explicit operator bool() const noexcept; constexpr bool has_value() const noexcept; constexpr const T& value() const&; constexpr T& value() &; constexpr const T&& value() const &&; constexpr T&& value() &&; constexpr const E& error() const&; constexpr E& error() &; constexpr const E&& error() const &&; constexpr E&& error() &&; template <class U> constexpr T value_or(U&&) const&; template <class U> constexpr T value_or(U&&) &&; // �.�.4.7, Expected equality operators template <class T1, class E1, class T2, class E2> friend constexpr bool operator==(const expected<T1, E1>& x, const expected<T2, E2>& y); template <class T1, class E1, class T2, class E2> friend constexpr bool operator!=(const expected<T1, E1>& x, const expected<T2, E2>& y); // �.�.4.8, Comparison with T template <class T1, class E1, class T2> friend constexpr bool operator==(const expected<T1, E1>&, const T2&); template <class T1, class E1, class T2> friend constexpr bool operator==(const T2&, const expected<T1, E1>&); template <class T1, class E1, class T2> friend constexpr bool operator!=(const expected<T1, E1>&, const T2&); template <class T1, class E1, class T2> friend constexpr bool operator!=(const T2&, const expected<T1, E1>&); // �.�.4.9, Comparison with unexpected<E> template <class T1, class E1, class E2> friend constexpr bool operator==(const expected<T1, E1>&, const unexpected<E2>&); template <class T1, class E1, class E2> friend constexpr bool operator==(const unexpected<E2>&, const expected<T1, E1>&); template <class T1, class E1, class E2> friend constexpr bool operator!=(const expected<T1, E1>&, const unexpected<E2>&); template <class T1, class E1, class E2> friend constexpr bool operator!=(const unexpected<E2>&, const expected<T1, E1>&); // �.�.4.10, Specialized algorithms template <class T1, class E1> friend void swap(expected<T1, E1>&, expected<T1, E1>&) noexcept(see below); private: bool has_val; // exposition only union { value_type val; // exposition only unexpected_type unexpect; // exposition only }; };
Any instance of expected<T, E>
at any given time either contains a value of
type T
or a value of type unexpected<E>
within their own storage.
Implementations are not permitted to use additional storage, such as dynamic
memory, to allocate the object of type T
or the object of type unexpected<E>
.
These objects shall be allocated in a region of the expected<T, E>
storage
suitably aligned for the types T
and unexpected<E>
. Members has_val
, val
and unexpect
are provided for exposition only. has_val
indicates whether the expected<T, E>
object is valued.
T
shall be void
or shall be an object type that is not array and shall satisfy the requirements
of Destructible
(Table 27).
E
shall be object type that is not array and shall satisfy the requirements of Destructible
(Table 27).
1.6. �.�.4.1 Constructors [expected.object.ctor]
constexpr expected();
Effects: Value-Initializes val
if T
is not void
.
Postconditions: bool(*this)
.
Throws: Any exception thrown by the selected constructor of T
.
Remarks: If value-initialization of T
is a constexpr constructor or T
is void
this constructor shall be constexpr. This constructor shall not participate in
overload resolution unless is_default_constructible_v<T>
is true
or T
is void
.
constexpr expected(const expected& rhs);
Effects: If bool(rhs)
, initializes val
as if
direct-non-list-initializing an object of type T
with the expression *rhs
if T
is not void
.
If !bool(rhs)
, initializes unexpect
as if
direct-non-list-initializing an object of type unexpected<E>
with the
expression unexpected(rhs.error())
.
Postconditions: bool(rhs) == bool(*this)
.
Throws: Any exception thrown by the selected constructors of T
or E
.
Remarks: This constructor shall be defined as deleted unless is_copy_constructible_v<T>
is true
or T
is void
and is_copy_constructible_v<E>
is true
. If is_trivially_copy_constructible_v<T>
is true
or T
is void
and is_trivially_copy_constructible_v<E>
is true
,
this constructor shall be a constexpr constructor.
constexpr expected(expected && rhs) noexcept(see below);
Effects: If bool(rhs)
, initializes val
as if
direct-non-list-initializing an object of type T
with the expression std::move(*rhs)
(if T
is not void
).
If !bool(rhs)
, initializes val
as if
direct-non-list-initializing an object of type unexpected<E>
with the
expression unexpected(std::move(rhs.error()))
.
bool(rhs)
is unchanged.
Postconditions: bool(rhs) == bool(*this)
.
Throws: Any exception thrown by the selected constructor of T
or E
.
Remarks: The expression inside noexcept
is equivalent to: is_nothrow_move_constructible_v<T>
is true
or T
is void
and is_nothrow_move_constructible_v<E>
is true
. This constructor shall not participate in
overload resolution unless is_move_constructible_v<T>
is true
and is_move_constructible_v<E>
is true
. If is_trivially_move_constructible_v<T>
is true
or T
is void
and is_trivially_move_constructible_v<E>
is true
,
this constructor shall be a constexpr constructor.
template <class U, class G> EXPLICIT constexpr expected(const expected<U, G>& rhs);
Effects: If bool(rhs)
, initializes val
as if
direct-non-list-initializing an object of type T
with the expression *rhs
,
if T
is not void
.
If !bool(rhs)
initializes unexpect
as if
direct-non-list-initializing an object of type unexpected<E>
with the
expression unexpected(rhs.error())
.
Postconditions: bool(rhs) == bool(*this)
.
Throws: Any exception thrown by the selected constructor of T
or E
.
Remarks: This constructor shall not participate in overload resolution unless: T
and U
are void
or
-
is_constructible_v<T, const U&>
istrue
, -
is_constructible_v<T, expected<U, G>&>
isfalse
, -
is_constructible_v<T, expected<U, G>&&>
isfalse
, -
is_constructible_v<T, const expected<U, G>&>
isfalse
, -
is_constructible_v<T, const expected<U, G>&&>
isfalse
, -
is_convertible_v<expected<U, G>&, T>
isfalse
, -
is_convertible_v<expected<U, G>&&, T>
isfalse
, -
is_convertible_v<const expected<U, G>&, T>
isfalse
and -
is_convertible_v<const expected<U, G>&&, T>
isfalse
and
-
is_constructible_v<E, const G&>
istrue
, -
is_constructible_v<unexpected<E>, expected<U, G>&>
isfalse
, -
is_constructible_v<unexpected<E>, expected<U, G>&&>
isfalse
, -
is_constructible_v<unexpected<E>, const expected<U, G>&>
isfalse
, -
is_constructible_v<unexpected<E>, const expected<U, G>&&>
isfalse
, -
is_convertible_v<expected<U, G>&, unexpected<E>>
isfalse
, -
is_convertible_v<expected<U, G>&&, unexpected<E>>
isfalse
, -
is_convertible_v<const expected<U, G>&, unexpected<E>>
isfalse
and -
is_convertible_v<const expected<U, G>&&, unexpected<E>>
isfalse
.
The constructor is explicit if and only if
-
T
andU
are notvoid
andis_convertible_v<U const&, T>
isfalse
or -
is_convertible_v<const G&, E>
isfalse
.template <class U, class G> EXPLICIT constexpr expected(expected<U, G>&& rhs);
Effects: If bool(rhs)
initializes val
as if
direct-non-list-initializing an object of type T
with the expression std::move(*rhs)
or nothing if T
is void
.
If !bool(rhs)
, initializes unexpect
as if
direct-non-list-initializing an object of type unexpected<E>
with the
expression unexpected(std::move(rhs.error()))
.
bool(rhs)
is unchanged.
Postconditions: bool(rhs) == bool(*this)
.
Throws: Any exception thrown by operations specified in the effect clause.
Remarks: This constructor shall not participate in overload resolution unless: T
and U
are void
or
-
is_constructible_v<T, U&&>
istrue
, -
is_constructible_v<T, expected<U, G>&>
isfalse
, -
is_constructible_v<T, expected<U, G>&&>
isfalse
, -
is_constructible_v<T, const expected<U, G>&>
isfalse
, -
is_constructible_v<T, const expected<U, G>&&>
isfalse
, -
is_convertible_v<expected<U, G>&, T>
isfalse
, -
is_convertible_v<expected<U, G>&&, T>
isfalse
, -
is_convertible_v<const expected<U, G>&, T>
isfalse
, and -
is_convertible_v<const expected<U, G>&&, T>
isfalse
.
and
-
is_constructible_v<E, G&&>
istrue
, -
is_constructible_v<unexpected<E>, expected<U, G>&>
isfalse
, -
is_constructible_v<unexpected<E>, expected<U, G>&&>
isfalse
, -
is_constructible_v<unexpected<E>, const expected<U, G>&>
isfalse
, -
is_constructible_v<unexpected<E>, const expected<U, G>&&>
isfalse
, -
is_convertible_v<expected<U, G>&, unexpected<E>>
isfalse
, -
is_convertible_v<expected<U, G>&&, unexpected<E>>
isfalse
, -
is_convertible_v<const expected<U, G>&, unexpected<E>>
isfalse
and -
is_convertible_v<const expected<U, G>&&, unexpected<E>>
isfalse
.
The constructor is explicit if and only if
-
T
andU
are notvoid
andis_convertible_v<U&&, T>
isfalse
or -
is_convertible_v<G&&, E>
isfalse
.template <class U = T> EXPLICIT constexpr expected(U&& v);
Effects: Initializes val
as if direct-non-list-initializing an
object of type T
with the expression std::forward<U>(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. This constructor shall not
participate in overload resolution unless
-
T
is notvoid
, -
is_constructible_v<T, U&&>
istrue
, -
is_same_v<remove_cvref_t<U>, in_place_t>
isfalse
, -
is_same_v<expected<T, E>, remove_cvref_t<U>>
isfalse
and -
is_same_v<unexpected<E>, remove_cvref_t<U>>
isfalse
.
The constructor is explicit if and only if is_convertible_v<U&&, T>
is false
.
template <class G = E> EXPLICIT constexpr expected(const unexpected<G>& e);
Effects: Initializes unexpect
as if direct-non-list-initializing
an object of type unexpected<E>
with the expression e
.
Postconditions: !bool(*this)
.
Throws: Any exception thrown by the selected constructor of E
.
Remark: If unexpected<E>
's selected constructor is a constexpr constructor,
this constructor shall be a constexpr constructor. This constructor shall not
participate in overload resolution unless is_constructible_v<E, const G&>
is true
. The
constructor is explicit if and only if is_convertible_v<const G&, E>
is false
.
template <class G = E> EXPLICIT constexpr expected(unexpected<G>&& e);
Effects: Initializes unexpect
as if direct-non-list-initializing
an object of type unexpected<E>
with the expression std::move(e)
.
Postconditions: !bool(*this)
.
Throws: Any exception thrown by the selected constructor of E
.
Remark: If unexpected<E>
's selected constructor is a constexpr constructor,
this constructor shall be a constexpr constructor. The expression inside noexcept
is equivalent to: is_nothrow_constructible_v<E, G&&>
is true
. This
constructor shall not participate in overload resolution unless is_constructible_v<E, G&&>
is true
. The constructor is explicit if and only if is_convertible_v<G&&, E>
is false
.
template <class... Args> constexpr explicit expected(in_place_t, Args&&... args);
Effects: Initializes val
as if direct-non-list-initializing an
object of type T
with the arguments std::forward<Args>(args)...
if T
is not void
.
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 T
is void
and sizeof...(Args) == 0
or T
is not void
and is_constructible_v<T, Args...>
is true
.
template <class U, class... Args> constexpr explicit expected(in_place_t, initializer_list<U> il, Args&&... args);
Effects: Initializes val
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 T
is not void
and is_constructible_v<T, initializer_list<U>&, Args...>
is true
.
template <class... Args> constexpr explicit expected(unexpect_t, Args&&... args);
Effects: Initializes unexpect
as if direct-non-list-initializing
an object of type unexpected<E>
with the arguments std::forward<Args>(args)...
.
Postconditions: !bool(*this)
.
Throws: Any exception thrown by the selected constructor of E
.
Remarks: If unexpected<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_v<E, Args&&...>
is true
.
template <class U, class... Args> constexpr explicit expected(unexpect_t, initializer_list<U> il, Args&&... args);
Effects: Initializes unexpect
as if direct-non-list-initializing
an object of type unexpected<E>
with the arguments il, std::forward<Args>(args)...
.
Postconditions: !bool(*this)
.
Throws: Any exception thrown by the selected constructor of E
.
Remarks: If unexpected<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_v<E, initializer_list<U>&, Args&&...>
is true
.
1.7. �.�.4.2 Destructor [expected.object.dtor]
~expected();
Effects: If T
is not void
and is_trivially_destructible_v<T>
is false
and bool(*this)
, calls val.~T()
. If is_trivially_destructible_v<E>
is false
and !bool(*this)
, calls unexpect.~unexpected<E>()
.
Remarks: If T
is void
or is_trivially_destructible_v<T>
is true
and is_trivially_destructible_v<E>
is true
then this destructor shall be a
trivial destructor.
1.8. �.�.4.3 Assignment [expected.object.assign]
expected& operator=(const expected& rhs) noexcept(see below);
Effects:
If bool(*this)
and bool(rhs)
,
-
assigns
*rhs
toval
ifT
is notvoid
;
otherwise if !bool(*this)
and !bool(rhs)
,
-
assigns
unexpected(rhs.error())
tounexpect
;
otherwise if bool(*this)
and !bool(rhs)
,
-
if
T
isvoid
-
initializes
unexpect
as if direct-non-list-initializing an object of typeunexpected<E>
withunexpected(rhs.error())
. Either -
the constructor didn’t throw, set
has_val
tofalse
, or -
the constructor did throw, and nothing was changed.
-
-
otherwise if
is_nothrow_copy_constructible_v<E>
istrue
-
destroys
val
by callingval.~T()
, -
initializes
unexpect
as if direct-non-list-initializing an object of typeunexpected<E>
withunexpected(rhs.error())
and sethas_val
tofalse
.
-
-
otherwise if
is_nothrow_move_constructible_v<E>
istrue
-
constructs a
unexpected<E> tmp
fromunexpected(rhs.error())
(this can throw), -
destroys
val
by callingval.~T()
, -
initializes
unexpect
as if direct-non-list-initializing an object of typeunexpected<E>
withstd::move(tmp)
and sethas_val
tofalse
.
-
otherwise
-
constructs a
T tmp
from*this
(this can throw), -
destroys
val
by callingval.~T()
, -
initializes
unexpect
as if direct-non-list-initializing an object of typeunexpected<E>
withunexpected(rhs.error())
. Either,-
the last constructor didn’t throw, set
has_val
tofalse
, or -
the last constructor did throw, so move-construct the
T
fromtmp
back into the expected storage (which can’t throw asis_nothrow_move_constructible_v<T>
istrue
), and rethrow the exception.
-
otherwise
-
if
T
isvoid
destroysunexpect
by callingunexpect.~unexpected<E>()
and sethas_val
totrue
, -
otherwise if
is_nothrow_copy_constructible_v<T>
istrue
-
destroys
unexpect
by callingunexpect.~unexpected<E>()
-
initializes
val
as if direct-non-list-initializing an object of typeT
with*rhs
;
-
-
otherwise if
is_nothrow_move_constructible_v<T>
istrue
-
constructs a
T tmp
from*rhs
(this can throw), -
destroys
unexpect
by callingunexpect.~unexpected<E>()
-
initializes
val
as if direct-non-list-initializing an object of typeT
withstd::move(tmp)
;
-
-
otherwise
-
constructs a
unexpected<E> tmp
fromunexpected(this->error())
(which can throw), -
destroys
unexpect
by callingunexpect.~unexpected<E>()
, -
initializes
val
as if direct-non-list-initializing an object of typeT
with*rhs
. Either, -
the constructor didn’t throw, set
has_val
totrue
, or -
the constructor did throw, so move-construct the
unexpected<E>
fromtmp
back into the expected storage (which can’t throw asis_nothrow_move_constructible_v<E>
istrue
), and rethrow the exception.
-
Returns: *this
.
Postconditions: bool(rhs) == bool(*this)
.
Throws: Any exception thrown by the operations specified in the effect clause.
Remarks: If any exception is thrown, bool(*this)
and bool(rhs)
remain unchanged.
If an exception is thrown during the call to T
's or unexpected<E>
's copy
constructor, no effect. If an exception is thrown during the call to T
's or unexpected<E>
's copy assignment, the state of its contained value is as defined
by the exception safety guarantee of T
's or unexpected<E>
's copy assignment.
This operator shall be defined as deleted unless
-
T
isvoid
andis_copy_assignable_v<E>
istrue
andis_copy_constructible_v<E>
istrue
or -
T
is notvoid
andis_copy_assignable_v<T>
istrue
andis_copy_constructible_v<T>
istrue
andis_copy_assignable_v<E>
istrue
andis_copy_constructible_v<E>
istrue
and (is_nothrow_move_constructible_v<E>
istrue
oris_nothrow_move_constructible_v<T>
istrue
).expected& operator=(expected&& rhs) noexcept(see below);
Effects:
If bool(*this)
and bool(rhs)
,
-
move assign
*rhs
toval
ifT
is notvoid
;
otherwise if !bool(*this)
and !bool(rhs)
,
-
move assign
unexpected(rhs.error())
tounexpect
;
otherwise if bool(*this)
and !bool(rhs)
,
-
if
T
isvoid
-
initializes
unexpect
as if direct-non-list-initializing an object of typeunexpected<E>
withunexpected(move(rhs).error())
. Either -
the constructor didn’t throw, set
has_val
tofalse
, or -
the constructor did throw, and nothing was changed.
-
-
otherwise if
is_nothrow_move_constructible_v<E>
istrue
-
destroys
val
by callingval.~T()
, -
initializes
unexpect
as if direct-non-list-initializing an object of typeunexpected<E>
withunexpected(std::move(rhs.error()))
;
-
-
otherwise
-
move constructs a
T tmp
from*this
(which can’t throw asT
is nothrow-move-constructible), -
destroys
val
by callingval.~T()
, -
initializes
unexpect
as if direct-non-list-initializing an object of typeunexpected_type<E>
withunexpected(std::move(rhs.error()))
. Either, -
The constructor didn’t throw, so mark the expected as holding a
unexpected_type<E>
, or -
The constructor did throw, so move-construct the
T
fromtmp
back into the expected storage (which can’t throw asT
is nothrow-move-constructible), and rethrow the exception.
-
otherwise !bool(*this)
and bool(rhs)
,
-
if
T
isvoid
destroysunexpect
by callingunexpect.~unexpected<E>()
-
otherwise if
is_nothrow_move_constructible_v<T>
istrue
-
destroys
unexpect
by callingunexpect.~unexpected<E>()
, -
initializes
val
as if direct-non-list-initializing an object of typeT
with*std::move(rhs)
;
-
-
otherwise
-
move constructs a
unpepected_type<E> tmp
fromunexpected(this->error())
(which can’t throw asE
is nothrow-move-constructible), -
destroys
unexpect
by callingunexpect.~unexpected<E>()
, -
initializes
val
as if direct-non-list-initializing an object of typeT
with*std::move(rhs)
. Either, -
The constructor didn’t throw, set
has_val
totrue
, or -
The constructor did throw, so move-construct the
unexpected<E>
fromtmp
back into the expected storage (which can’t throw asE
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_v<T>
is true
and is_nothrow_move_constructible_v<T>
is true
.
If any exception is thrown, 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 unexpect
is as defined by
the exception safety guarantee of E
's copy assignment.
This operator shall be defined as deleted unless
-
T
isvoid
andis_nothrow_move_constructible_v<E>
istrue
andis_nothrow_move_assignable_v<E>
istrue
.
or
-
T
is notvoid
andis_move_constructible_v<T>
istrue
andis_move_assignable_v<T>
istrue
andis_nothrow_move_constructible_v<E>
istrue
andis_nothrow_move_assignable_v<E>
istrue
.template <class U = T> expected<T, E>& operator=(U&& v);
Effects:
If bool(*this)
, assigns std::forward<U>(v)
to val
;
otherwise if is_nothrow_constructible_v<T, U&&>
is true
-
destroys
unexpect
by callingunexpect.~unexpected<E>()
, -
initializes
val
as if direct-non-list-initializing an object of typeT
withstd::forward<U>(v)
and -
set
has_val
totrue
;
otherwise
-
move constructs a
unexpected<E> tmp
fromunexpected(this->error())
(which can’t throw asE
is nothrow-move-constructible), -
destroys
unexpect
by callingunexpect.~unexpected<E>()
, -
initializes
val
as if direct-non-list-initializing an object of typeT
withstd::forward<U>(v)
. Either,-
the constructor didn’t throw, set
has_val
totrue
, that is sethas_val
totrue
, or -
the constructor did throw, so move construct the
unexpected<E>
fromtmp
back into the expected storage (which can’t throw asE
is nothrow-move-constructible), and re-throw the exception.
-
Returns: *this
.
Postconditions: bool(*this)
.
Remarks: If any exception is thrown, 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.
This function shall not participate in overload resolution unless:
-
is_void_v<T>
isfalse
and -
is_same_v<expected<T,E>, remove_cvref_t<U>>
isfalse
and -
conjunction_v<is_scalar<T>, is_same<T, decay_t<U>>>
isfalse
, -
is_constructible_v<T, U>
istrue
, -
is_assignable_v<T&, U>
istrue
and -
is_nothrow_move_constructible_v<E>
istrue
.template <class G = E> expected<T, E>& operator=(const unexpected<G>& e);
Effects:
If !bool(*this)
, assigns unexpected(e.error())
to unexpect
;
otherwise
-
destroys
val
by callingval.~T()
ifT
is notvoid
, -
initializes
unexpect
as if direct-non-list-initializing an object of typeunexpected<E>
withunexpected(e.error())
and sethas_val
tofalse
.
Returns: *this
.
Postconditions: !bool(*this)
.
Remarks: If any exception is thrown, bool(*this)
remains unchanged.
This signature shall not participate in overload resolution unless is_nothrow_copy_constructible_v<E>
is true
and is_move_assignable_v<E>
is true
.
expected<T, E>& operator=(unexpected<G> && e);
Effects:
If !bool(*this)
, move assign unexpected(e.error())
to unexpect
;
otherwise
-
destroys
val
by callingval.~T()
ifT
is notvoid
, -
initializes
unexpect
as if direct-non-list-initializing an object of typeunexpected<E>
withunexpected(std::move(e.error()))
and sethas_val
tofalse
.
Returns: *this
.
Postconditions: !bool(*this)
.
Remarks: If any exception is thrown, bool(*this)
remains unchanged.
This signature shall not participate in overload resolution unless is_nothrow_move_constructible_v<E>
is true
and is_move_assignable_v<E>
is true
.
void expected<void,E>::emplace();
Effects:
If !bool(*this)
-
destroys
unexpect
by callingunexpect.~unexpected<E>()
, -
set
has_val
totrue
Postconditions: bool(*this)
.
Throws: Nothing
template <class... Args> T& emplace(Args&&... args);
Effects:
If bool(*this)
, assigns val
as if
constructing an object of type T
with the arguments std::forward<Args>(args)...
otherwise if is_nothrow_constructible_v<T, Args&&...>
is true
-
destroys
unexpect
by callingunexpect.~unexpected<E>()
, -
initializes
val
as if direct-non-list-initializing an object of typeT
withstd::forward<Args>(args)...
and -
set
has_val
totrue
;
otherwise if is_nothrow_move_constructible_v<T>
is true
-
constructs a
T tmp
fromstd::forward<Args>(args)...
(which can throw), -
destroys
unexpect
by callingunexpect.~unexpected<E>()
, -
initializes
val
as if direct-non-list-initializing an object of typeT
withstd::move(tmp)
(which cannot throw) and -
set
has_val
totrue
;
otherwise
-
move constructs a
unexpected<E> tmp
fromunexpected(this->error())
, -
destroys
unexpect
by callingunexpect.~unexpected<E>()
, -
initializes
val
as if direct-non-list-initializing an object of typeT
withstd::forward<Args>(args)...
. Either,-
the constructor didn’t throw, set
has_val
totrue
, or -
the constructor did throw, so move-construct the
unexpected<E>
fromtmp
back into the expected storage (which can’t throw asE
is nothrow-move-constructible), and re-throw the exception.
-
Postconditions: bool(*this)
.
Returns: A reference to the new contained value val
.
Throws: Any exception thrown by the operations specified in the effect clause.
Remarks: If an exception is thrown during the call to T
's assignment, nothing
changes.
This signature shall not participate in overload resolution unless: T
is not void
and is_nothrow_constructible_v<T, Args&&...>
is true
.
template <class U, class... Args> T& emplace(initializer_list<U> il, Args&&... args);
Effects:
If bool(*this)
, assigns val
as if
constructing an object of type T
with the arguments il, std::forward<Args>(args)...
otherwise if is_nothrow_constructible_v<T, initializer_list<U>&, Args&&...>
is true
-
destroys
unexpect
by callingunexpect.~unexpected<E>()
, -
initializes
val
as if direct-non-list-initializing an object of typeT
withil, std::forward<Args>(args)...
and -
set
has_val
totrue
;
otherwise if is_nothrow_move_constructible_v<T>
is true
-
constructs a
T tmp
fromil, std::forward<Args>(args)...
(which can throw), -
destroys
unexpect
by callingunexpect.~unexpected<E>()
, -
initializes
val
as if direct-non-list-initializing an object of typeT
withstd::move(tmp)
(which cannot throw) and -
set
has_val
totrue
;
otherwise
-
move constructs a
unexpected<E> tmp
fromunexpected(this->error())
, -
destroys
unexpect
by callingunexpect.~unexpected<E>()
, -
initializes
val
as if direct-non-list-initializing an object of typeT
withil, std::forward<Args>(args)...
. Either,-
the constructor didn’t throw, set
has_val
totrue
, or -
the constructor did throw, so move-construct the
unexpected<E>
fromtmp
back into the expected storage (which can’t throw asE
is nothrow-move-constructible), and re-throw the exception.
-
Postconditions: bool(*this)
.
Returns: A reference to the new contained value val
.
Throws: Any exception thrown by the operations specified in the effect clause.
Remarks: If an exception is thrown during the call to T
's assignment nothing
changes.
The function shall not participate in overload resolution unless: T
is not void
and is_nothrow_constructible_v<T, initializer_list<U>&, Args&&...>
is true
.
1.9. �.�.4.4 Swap [expected.object.swap]
void swap(expected<T, E>& rhs) noexcept(see below);
Effects: if bool(*this)
and bool(rhs)
,
-
if
T
is notvoid
callsusing std::swap; swap(val, rhs.val)
,
otherwise if !bool(*this)
and !bool(rhs)
,
-
calls
using std::swap; swap(unexpect, rhs.unexpect)
,
otherwise if !bool(*this)
and bool(rhs)
,
-
calls
rhs.swap(*this)
,
otherwise
-
if
T
isvoid
-
initializes
unexpect
as if direct-non-list-initializing an object of typeunexpected<E>
withunexpected(std::move(rhs))
. Either -
the constructor didn’t throw, set
has_val
tofalse
, destroysrhs.unexpect
by callingrhs.unexpect.~unexpected<E>()
setrhs.has_val
totrue
. -
the constructor did throw, rethrow the exception.
-
-
otherwise if
is_nothrow_move_constructible_v<E>
istrue
,-
the
unexpect
ofrhs
is moved to a temporary variabletmp
of typeunexpected_type
, -
followed by destruction of
unexpect
as if byrhs.unexpect.~unexpected<E>()
, -
rhs.val
is direct-initialized fromstd::move(*this)
. Either-
the constructor didn’t throw
-
destroy
val
as if bythis->val.~T()
, -
the
unexpect
ofthis
is direct-initialized fromstd::move(tmp)
, after this,this
does not contain a value; andbool(rhs)
.
-
-
the constructor did throw, so move-construct the
unexpected<E>
fromtmp
back into the expected storage (which can’t throw asE
is nothrow-move-constructible), and re-throw the exception.
-
-
-
otherwise if
is_nothrow_move_constructible_v<T>
istrue
,-
val
is moved to a temporary variabletmp
of typeT
, -
followed by destruction of
val
as if byval.~T()
, -
the
unexpect
is direct-initialized fromunexpected(std::move(other.error()))
. Either-
the constructor didn’t throw
-
destroy
rhs.unexpect
as if byrhs.unexpect.~unexpected<E>()
, -
rhs.val
is direct-initialized fromstd::move(tmp)
, after this,this
does not contain a value; andbool(rhs)
.
-
-
the constructor did throw, so move-construct the
T
fromtmp
back into the expected storage (which can’t throw asT
is nothrow-move-constructible), and re-throw the exception.
-
-
-
otherwise, the function is not defined.
Throws: Any exceptions that the expressions in the Effects clause throw.
Remarks: The expression inside noexcept is equivalent to: is_nothrow_move_constructible_v<T>
is true
and is_nothrow_swappable_v<T>
is true
and is_nothrow_move_constructible_v<E>
is true
and is_nothrow_swappable_v<E>
is true
.
The function shall not
participate in overload resolution unless:
-
Lvalues of type
T
shall beSwappable
, -
Lvalues of type
E
shall beSwappable
, -
is_void_v<T>
istrue
or -
is_void_v<T>
isfalse
and-
is_move_constructible_v<T>
istrue
, -
is_move_constructible_v<E>
istrue
and -
is_move_constructible_v<E>
istrue
oris_move_constructible_v<T>
istrue
.
-
1.10. �.�.4.5 Observers [expected.object.observe]
constexpr const T* operator->() const; T* operator->();
Requires: bool(*this)
.
Returns: addressof(val)
.
Remarks: Unless T
is a user-defined type with overloaded unary operator&
,
the first operator shall be a constexpr function.
The operator shall not participate in overload resolution unless: T
is not void
.
constexpr const T& operator *() const&; T& operator *() &;
Requires: bool(*this)
.
Returns: val
.
Remarks: The first operator shall be a constexpr function.
The operator shall not participate in overload resolution unless: T
is not void
.
constexpr T&& operator *() &&; constexpr const T&& operator *() const&&;
Requires: bool(*this)
.
Returns: std::move(val)
.
Remarks: This operator shall be a constexpr function.
The operator shall not participate in overload resolution unless: T
is not void
.
constexpr explicit operator bool() noexcept;
Returns: has_val
.
Remarks: This operator shall be a constexpr function.
constexpr bool has_value() const noexcept;
Returns: has_val
.
Remarks: This function shall be a constexpr function.
constexpr void expected<void, E>::value() const;
Throws: bad_expected_access(error())
if !bool(*this)
.
constexpr const T& expected::value() const&; constexpr T& expected::value() &;
Returns: val
, if bool(*this)
.
Throws: bad_expected_access(error())
if !bool(*this)
.
Remarks: These functions shall be constexpr functions.
The operator shall not participate in overload resolution unless: T
is not void
.
constexpr T&& expected::value() &&; constexpr const T&& expected::value() const&&;
Returns: std::move(val)
, if bool(*this)
.
Throws: bad_expected_access(error())
if !bool(*this)
.
Remarks: These functions shall be constexpr functions.
The operator shall not participate in overload resolution unless: T
is not void
.
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: std::move(unexpect.value())
.
Remarks: The first function shall be a constexpr function.
template <class U> constexpr T value_or(U&& v) const&;
Returns: bool(*this) ? **this : static_cast<T>(std::forward<U>(v));
.
Remarks: If is_copy_constructible_v<T> is
true&& is_convertible_v<U&&, T>
is false
the program is ill-formed.
template <class U> constexpr T value_or(U&& v) &&;
Returns: bool(*this) ? std::move(**this) : static_cast<T>(std::forward<U>(v));
.
Remarks: If is_move_constructible_v<T>
is true
and is_convertible_v<U&&, T>
is false
the program is ill-formed.
1.11. �.�.4.6 Expected Equality operators [expected.equality_op]
template <class T1, class E1, class T2, class E2> friend constexpr bool operator==(const expected<T1, E1>& x, const expected<T2, E2>& y);
Requires: The expressions *x == *y
and unexpected(x.error()) == unexpected(y.error())
shall be well-formed and its result
shall be convertible to bool
.
Returns: If bool(x) != bool(y)
, false
; otherwise if bool(x) == false
, x.error() == y.error()
; otherwise true
if T1
and T2
are void
or *x == *y
otherwise.
Remarks: Specializations of this function template, for which T1
and T2
are void
or *x == *y
and x.error() == y.error()
are core constant expression, shall be constexpr
functions.
template <class T1, class E1, class T2, class E2> constexpr bool operator!=(const expected<T1, E1>& x, const expected<T2, E2>& y);
Requires: The expressions *x != *y
and x.error() != y.error()
shall be
well-formed and its result shall be convertible to bool
.
Returns: If bool(x) != bool(y)
, true
; otherwise if bool(x) == false
, x.error() != y.error()
; otherwise true
if T1
and T2
are void
or *x != *y
.
Remarks: Specializations of this function template, for which T1
and T2
are void
or *x != *y
and x.error() != y.error()
are core
constant expression, shall be constexpr functions.
1.12. �.�.4.7 Comparison with T
[expected.comparison_T]
template <class T1, class E1, class T2> constexpr bool operator==(const expected<T1, E1>& x, const T2& v); template <class T1, class E1, class T2> constexpr bool operator==(const T2& v, const expected<T1, E1>& x);
Requires: T1
and T2
are not void
and the expression *x == v
shall be well-formed
and its result shall be convertible to bool
. [ Note: T1
need not be EqualityComparable. - end note]
Returns: bool(x) ? *x == v : false;
.
template <class T1, class E1, class T2> constexpr bool operator!=(const expected<T1, E1>& x, const T2& v); template <class T1, class E1, class T2> constexpr bool operator!=(const T2& v, const expected<T1, E1>& x);
Requires: T1
and T2
are not void
and the expression *x == v
shall be well-formed
and its result shall be convertible to bool
. [ Note: T1
need not be EqualityComparable. - end note]
Returns: bool(x) ? *x != v : false;
.
1.13. �.�.4.8 Comparison with unexpected<E>
[expected.comparison_unexpected_E]
template <class T1, class E1, class E2> constexpr bool operator==(const expected<T1, E1>& x, const unexpected<E2>& e); template <class T1, class E1, class E2> constexpr bool operator==(const unexpected<E2>& e, const expected<T1, E1>& x);
Requires: The expression unexpected(x.error()) == e
shall be well-formed and
its result shall be convertible to bool
.
Returns: bool(x) ? false : unexpected(x.error()) == e;
.
template <class T1, class E1, class E2> constexpr bool operator!=(const expected<T1, E1>& x, const unexpected<E2>& e); template <class T1, class E1, class E2> constexpr bool operator!=(const unexpected<E2>& e, const expected<T1, E1>& x);
Requires: The expression unexpected(x.error()) != e
shall be well-formed and
its result shall be convertible to bool
. Returns: bool(x) ? true : unexpected(x.error()) != e;
.
1.14. �.�.4.9 Specialized algorithms [expected.specalg]
template <class T1, class E1> void swap(expected<T1, E1>& x, expected<T1, E1>& y) noexcept(noexcept(x.swap(y)));
Effects: Calls x.swap(y)
.
Remarks: This function shall not participate in overload resolution unless T1
is void
or is_move_constructible_v<T1>
is true
, is_swappable_v<T1>
is true
and is_move_constructible_v<E1>
is true
and is_swappable_v<E1>
is true
.
1.15. �.�.5 Unexpected objects [expected.unexpected]
1.16. �.�.5.1 General [expected.unexpected.general]
This subclause describes class template unexpected
that
represents unexpected objects.
1.17. �.�.5.2 Class template unexpected
[expected.unexpected.object]
template <class E> class unexpected { public: constexpr unexpected(const unexpected&) = default; constexpr unexpected(unexpected&&) = default; template <class... Args> constexpr explicit unexpected(in_place_t, Args&&...); template <class U, class... Args> constexpr explicit unexpected(in_place_t, initializer_list<U>, Args&&...); template <class Err = E> constexpr explicit unexpected(Err&&); template <class Err> constexpr EXPLICIT unexpected(const unexpected<Err>&); template <class Err> constexpr EXPLICIT unexpected(unexpected<Err>&&); constexpr unexpected& operator=(const unexpected&) = default; constexpr unexpected& operator=(unexpected&&) = default; template <class Err = E> constexpr unexpected& operator=(const unexpected<Err>&); template <class Err = E> constexpr unexpected& operator=(unexpected<Err>&&); constexpr const E& value() const& noexcept; constexpr E& value() & noexcept; constexpr const E&& value() const&& noexcept; constexpr E&& value() && noexcept; void swap(unexpected& other) noexcept(see bellow); template <class E1, class E2> friend constexpr bool operator==(const unexpected<E1>&, const unexpected<E2>&); template <class E1, class E2> constexpr bool operator!=(const unexpected<E1>&, const unexpected<E2>&); template <class E1> friend void swap(unexpected<E1>& x, unexpected<E1>& y) noexcept(noexcept(x.swap(y))); private: E val; // exposition only }; template <class E> unexpected(E) -> unexpected<E>;
�.�.5.2.1 Constructors [expected.unexpected.ctor] {#expected.unexpected.ctor}
template <class Err> constexpr explicit unexpected(Err&& e);
Effects: Initializes val
as if direct-non-list-initializing an
object of type E
with the expression std::forward<Err>(e)
.
Throws: Any exception thrown by the selected constructor of E
.
Remarks:
If E’s selected constructor is a constexpr constructor, this constructor shall be a constexpr constructor.
This constructor shall not participate in overload resolution unless is_constructible_v<E, Err&&>
is true
, is_same_v<remove_cvref_t<U>, in_place_t>
is false
and is_same_v<remove_cvref_t<U>, unexpected>
is false
.
template <class... Args> constexpr explicit unexpected(in_place_t, Args&&...);
Effects: Initializes val
as if direct-non-list-initializing an
object of type E
with the arguments std::forward<Args>(args)...
.
Throws: Any exception thrown by the selected constructor of E
.
Remarks:
If E’s selected constructor is a constexpr constructor, this constructor shall be a constexpr constructor.
This constructor shall not participate in overload resolution unless is_constructible_v<E, Args&&...>
is true
.
template <class U, class... Args> constexpr explicit unexpected(in_place_t, initializer_list<U>, Args&&...);
Effects: Initializes val
as if direct-non-list-initializing an
object of type E
with the arguments il, std::forward<Args>(args)...
.
Throws: Any exception thrown by the selected constructor of E
.
Remarks:
If E’s selected constructor is a constexpr constructor, this constructor shall be a constexpr constructor.
This constructor shall not participate in overload resolution unless is_constructible_v<E, initializer_list<U>&, Args&&...>
is true
.
template <class Err> constexpr EXPLICIT unexpected(const unexpected<Err>& e);
Effects: Initializes val
as if direct-non-list-initializing an
object of type E
with the expression e.val
.
Throws: Any exception thrown by the selected constructor of E
.
Remarks: This constructor participates in overload resolution unless
-
std::is_constructible_v<E, Err>
istrue
, -
is_constructible_v<E, unexpected<Err>&>
isfalse
, -
is_constructible_v<E, unexpected<Err>&&>
isfalse
, -
is_constructible_v<E, const unexpected<Err>&>
isfalse
, -
is_constructible_v<E, const unexpected<Err>&&>
isfalse
, -
is_convertible_v<unexpected<Err>&, E>
isfalse
, -
is_convertible_v<unexpected<Err>&&, E>
isfalse
, -
is_convertible_v<const unexpected<Err>&, E>
isfalse
, and -
is_convertible_v<const unexpected<Err>&&, E>
isfalse
.
This constructor is explicit
if and only if std::is_convertible_v<Err, E>
is false
.
template <class Err> constexpr EXPLICIT unexpected(unexpected<Err> && e);
Effects: Initializes val
as if direct-non-list-initializing an
object of type E
with the expression std::move(e.val)
.
Throws: Any exception thrown by the selected constructor of E
.
Remarks: This constructor participates in overload resolution unless
-
std::is_constructible_v<E, Err&&>
istrue
, -
is_constructible_v<E, unexpected<Err>&>
isfalse
, -
is_constructible_v<E, unexpected<Err>&&>
isfalse
, -
is_constructible_v<E, const unexpected<Err>&>
isfalse
, -
is_constructible_v<E, const unexpected<Err>&&>
isfalse
, -
is_convertible_v<unexpected<Err>&, E>
isfalse
, -
is_convertible_v<unexpected<Err>&&, E>
isfalse
, -
is_convertible_v<const unexpected<Err>&, E>
isfalse
, and -
is_convertible_v<const unexpected<Err>&&, E>
isfalse
.
This constructor is explicit
if and only if std::is_convertible_v<Err&&, E>
is false
�.�.5.2.2 Assignment [expected.unexpected.assign] {#expected.unexpected.assign}
�.�.5.2.3 Observers [expected.unexpected.observe] {#expected.unexpected.observe}
constexpr const E& value() const &; constexpr E& value() &;
Returns: val
.
constexpr E&& value() &&; constexpr E const&& value() const&&;
Returns: std::move(val)
.
�.�.5.2.4 Swap [expected.unexpected.ctor] {#expected.unexpected.swap}
void swap(unexpected& other) noexcept(see bellow);
Requires: is_swappable_v<E>
is true
Effects: Equivalent to using std::swap; swap(val, other.val);
.
Throws:
Remarks: The expression inside noexcept
is equivalent to: is_nothrow_swappable_v<E>
is true
.
1.18. �.�.5.2.5 Equality operators [expected.unexpected.equality_op]
template <class E1, class E2> friend constexpr bool operator==(const unexpected<E1>& x, const unexpected<E2>& y);
Returns: x.value() == y.value()
.
template <class E1, class E2> friend constexpr bool operator!=(const unexpected<E1>& x, const unexpected<E2>& y);
Returns: x.value() != y.value()
.
�.�.5.2.5 Specialized algorithms [expected.unexpected.specalg] {#expected.unexpected.specalg}
template <class E1> friend void swap(unexpected<E1>& x, unexpected<E1>& y) noexcept(noexcept(x.swap(y)));
Effects: Equivalent to x.swap(y)
.
Remarks: This function shall not participate in overload resolution unless is_swappable_v<E1>
is true
.
1.19. �.�.6 Template Class bad_expected_access
[expected.bad_expected_access]
template <class E> class bad_expected_access : public bad_expected_access<void> { public: explicit bad_expected_access(E); virtual const char* what() const noexcept override; E& error() &; const E& error() const&; E&& error() &&; const E&& error() const&&; private: E val; // exposition only };
Wondering if we just need an const &
overload as we do for system_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 expected
object that contains an unexpected<E>
.
bad_expected_access::bad_expected_access(E e);
Effects: Initialize val
with e
.
Postconditions: what()
returns an implementation-defined NTBS.
const E& error() const&; E& error() &;
Returns: val;
E&& error() &&; const E&& error() const &&;
Returns: std::move(val);
virtual const char* what() const noexcept override;
Returns: An implementation-defined NTBS.
1.20. �.�.7 Class bad_expected_access<void>
[expected.bad_expected_access_base]
template <> class bad_expected_access<void> : public exception { public: explicit bad_expected_access(); };
1.21. �.�.8 unexpect
tag [expected.unexpect]
struct unexpect_t { explicit unexpect_t() = default; }; inline constexpr unexpect_t unexpect{};
2. Open Questions
std::expected
is a vocabulary type with an opinionated design and a proven
record under varied forms in a multitude of codebases. Its current form has
undergone multiple revisions and received substantial feedback, falling roughly
in the following categories:
-
Ergonomics: is this the right way to expose such functionality?
-
Disappointment: should we expose this in the Standard, given C++'s existing error handling mechanisms?
-
STL usage: should the Standard Template Library adopt this class, at which pace, and where?
LEWG and EWG have nonetheless reached consensus that a class of this general approach is probably desirable, and the only way to truly answer these questions is to try it out in a TS and ask for explicit feedback from developers. The authors hope that developers will provide new information which they’ll be able to communicate to the Committee.
Here are open questions, and questions which the Committee thinks are settled and which new information can justify revisiting.
2.1. Ergonomics
-
Name:
-
Is
expected
the right name? -
Does it express intent both as a consumer and a producer?
-
-
Is
E
a salient property ofexpected
? -
Is
expected<void, E>
clear on what it expresses as a return type? -
Would it make sense for
expected
to support containing bothT
andE
(in some designs, either one of them being optional), or is this use case better handled by a separate proposal? -
Is the order of parameters
<T, E>
appropriate? -
Is usage of
expected
"viral" in a codebase, or can it be adopted incrementally? -
-
Missing
expected<T,E>::emplace
forunexpected<E>
. -
The in-place construction from a
unexpected<E>
using theunexpet
tag doesn’t conveys the in-place nature of this constructor. -
Do we need in-place using indexes, as variant?
-
-
Comparisons:
-
Are
==
and!=
useful? -
Should other comparisons be provided?
-
What usages of
expected
mandate putting instances in amap
, or other such container? -
Should
hash
be provided? -
What usages of
expected
mandate putting instances in anunordered_map
, or other such container? -
Should
expected<T, E>
always be comparable ifT
is comparable, even ifE
is not comparable?
-
-
Error type
E
:-
Have
expected<T, void>
andunexpected<void>
a sense? -
E
template parameter has no default. Should it? -
Should
expected
be specialized for particularE
types such asexception_ptr
and how? -
Should
expected
handleE
types with a built-in "success" value any differently and how? -
expected
is not implicitly constructible from anE
, even when unambiguous fromT
, because as a vocabulary type it wants unexpected error construction to be verbose, and require hopping through anunexpected
. Is the verbosity extraneous?
-
-
Does usage of this class cause a meaningful performance impact compared to using error codes?
-
The accessor design offers a terse unchecked dereference operator (expected to be used alongside the implicit
bool
conversion), as well asvalue()
anderror()
accessors which are checked. Is that a gotcha, or is it similar enough to classes such asoptional
to be unsurprising? -
Is
bad_expected_access
the right thing to throw? -
Should some members be
nodiscard
?
2.2. Disappointment
C++ already supports exceptions and error codes, expected
would be a third
kind of error handling.
-
where does
expected
work better than either exceptions or error handling? -
expected
was designed to be particularly well suited to APIs which require their immediate caller to consider an error scenario. Do it succeed in that purpose? -
Do codebases successfully compose these three types of error handling?
-
Is debuggability any harder?
-
Is it easy to teach C++ as a whole with a third type of error handling?
2.3. STL Usage
-
Should
expected
be used in the STL at the same time as it gets standardized? -
Where, considering
std2
may be a good place to change APIs?