std::expected
monadic ops with move-only error_type
Section: 22.8.6.7 [expected.object.monadic] Status: WP Submitter: Jonathan Wakely Opened: 2023-05-25 Last modified: 2023-11-22
Priority: Not Prioritized
View all other issues in [expected.object.monadic].
View all issues with WP status.
Discussion:
The monadic ops for std::expected
are specified in terms of calls
to value()
and error()
, but LWG 3843
("std::expected<T,E>::value()&
assumes E
is copy constructible") added additional Mandates requirements to
value()
. This means that you can never call value()
for a move-only error_type
, even the overloads of
value()
with rvalue ref-qualifiers.
The changes to value()
are because it needs to be able to throw a
bad_expected_access<E>
which requires a copyable E
.
But in the monadic ops we know it can't throw, because we always check.
All the monadic ops are of the form:
if (has_value()) do something with value(); else do something with error();
We know that value()
won't throw here, but because we use
"Effects: Equivalent to ..." the requirement for E
to be copyable is inherited from value()
.
Should we have changed the monadic ops to use operator*()
instead of value()
?
For example, for the first and_then
overloads the change would be:
-4- Effects: Equivalent to:if (has_value()) return invoke(std::forward<F>(f),
value()**this); else return U(unexpect, error());
[2023-06-01; Reflector poll]
Set status to Tentatively Ready after seven votes in favour during reflector poll.
[2023-06-17 Approved at June 2023 meeting in Varna. Status changed: Voting → WP.]
Proposed resolution:
This wording is relative to N4950.
For each Effects: element in 22.8.6.7 [expected.object.monadic],
replace value()
with **this
as indicated:
template<class F> constexpr auto and_then(F&& f) &; template<class F> constexpr auto and_then(F&& f) const &;
-1- Let
U
beremove_cvref_t<invoke_result_t<F, decltype(
.value()**this)>>-2- Constraints:
is_constructible_v<E, decltype(error())>
istrue
.-3- Mandates:
U
is a specialization ofexpected
andis_same_v<U::error_type, E>
istrue
.-4- Effects: Equivalent to:
if (has_value()) return invoke(std::forward<F>(f),
value()**this); else return U(unexpect, error());template<class F> constexpr auto and_then(F&& f) &&; template<class F> constexpr auto and_then(F&& f) const &&;
-5- Let
U
beremove_cvref_t<invoke_result_t<F, decltype(std::move(
.value()**this))>>-6- Constraints:
is_constructible_v<E, decltype(std::move(error()))>
istrue
.-7- Mandates:
U
is a specialization ofexpected
andis_same_v<U::error_type, E>
istrue
.-8- Effects: Equivalent to:
if (has_value()) return invoke(std::forward<F>(f), std::move(
value()**this)); else return U(unexpect, std::move(error()));template<class F> constexpr auto or_else(F&& f) &; template<class F> constexpr auto or_else(F&& f) const &;
-9- Let
G
beremove_cvref_t<invoke_result_t<F, decltype(error())>>
.-10- Constraints:
is_constructible_v<T, decltype(
isvalue()**this)>true
.-11- Mandates:
G
is a specialization ofexpected
andis_same_v<G::value_type, T>
istrue
.-12- Effects: Equivalent to:
if (has_value()) return G(in_place,
value()**this); else return invoke(std::forward<F>(f), error());template<class F> constexpr auto or_else(F&& f) &&; template<class F> constexpr auto or_else(F&& f) const &&;
-13- Let
G
beremove_cvref_t<invoke_result_t<F, decltype(std::move(error()))>>
.-14- Constraints:
is_constructible_v<T, decltype(std::move(
isvalue()**this))>true
.-15- Mandates:
G
is a specialization ofexpected
andis_same_v<G::value_type, T>
istrue
.-16- Effects: Equivalent to:
if (has_value()) return G(in_place, std::move(
value()**this)); else return invoke(std::forward<F>(f), std::move(error()));template<class F> constexpr auto transform(F&& f) &; template<class F> constexpr auto transform(F&& f) const &;
-17- Let
U
beremove_cv_t<invoke_result_t<F, decltype(
.value()**this)>>-18- Constraints:
is_constructible_v<E, decltype(error())>
istrue
.-19- Mandates:
U
is a valid value type forexpected
. Ifis_void_v<U>
isfalse
, the declarationis well-formed.U u(invoke(std::forward<F>(f),
value()**this));-20- Effects:
- (20.1) — If
has_value()
isfalse
, returnsexpected<U, E>(unexpect, error())
.- (20.2) — Otherwise, if
is_void_v<U>
isfalse
, returns anexpected<U, E>
object whosehas_val
member istrue
andval
member is direct-non-list-initialized withinvoke(std::forward<F>(f),
.value()**this)- (20.3) — Otherwise, evaluates
invoke(std::forward<F>(f),
and then returnsvalue()**this)expected<U, E>()
.template<class F> constexpr auto transform(F&& f) &&; template<class F> constexpr auto transform(F&& f) const &&;
-21- Let
U
beremove_cv_t<invoke_result_t<F, decltype(std::move(
.value()**this))>>-22- Constraints:
is_constructible_v<E, decltype(std::move(error()))>
istrue
.-23- Mandates:
U
is a valid value type forexpected
. Ifis_void_v<U>
isfalse
, the declarationis well-formedU u(invoke(std::forward<F>(f), std::move(
value()**this)));for some invented variable.u
[Drafting Note: The removal of "for some invented variable u" in paragraph 23 is a drive-by fix for consistency with paragraphs 19, 27 and 31.]
-24- Effects:
- (24.1) — If
has_value()
isfalse
, returnsexpected<U, E>(unexpect, error())
.- (24.2) — Otherwise, if
is_void_v<U>
isfalse
, returns anexpected<U, E>
object whosehas_val
member istrue
andval
member is direct-non-list-initialized withinvoke(std::forward<F>(f), std::move(
.value()**this))- (24.3) — Otherwise, evaluates
invoke(std::forward<F>(f), std::move(
and then returnsvalue()**this))expected<U, E>()
.template<class F> constexpr auto transform_error(F&& f) &; template<class F> constexpr auto transform_error(F&& f) const &;
-25- Let
G
beremove_cv_t<invoke_result_t<F, decltype(error())>>
.-26- Constraints:
is_constructible_v<T, decltype(
isvalue()**this)>true
.-27- Mandates:
G
is a valie template argument forunexpected
( [unexpected.un.general]) and the declarationis well-formed.G g(invoke(std::forward<F>(f), error()));
-28- Returns: If
has_value()
istrue
,expected<T, G>(in_place,
; otherwise, anvalue()**this);expected<T, G>
object whosehas_val
member isfalse
andunex
member is direct-non-list-initialized withinvoke(std::forward<F>(f), error())
.template<class F> constexpr auto transform_error(F&& f) &&; template<class F> constexpr auto transform_error(F&& f) const &&;
-29- Let
G
beremove_cv_t<invoke_result_t<F, decltype(std::move(error()))>>
.-30- Constraints:
is_constructible_v<T, decltype(std::move(
isvalue()**this))>true
.-31- Mandates:
G
is a valie template argument forunexpected
( [unexpected.un.general]) and the declarationis well-formed.G g(invoke(std::forward<F>(f), std::move(error())));
-32- Returns: If
has_value()
istrue
,expected<T, G>(in_place, std::move(
; otherwise, anvalue()**this));expected<T, G>
object whosehas_val
member isfalse
andunex
member is direct-non-list-initialized withinvoke(std::forward<F>(f), std::move(error()))
.