This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of WP status.
std::expected monadic ops with move-only error_typeSection: 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(i)
("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
Uberemove_cvref_t<invoke_result_t<F, decltype(.value()**this)>>-2- Constraints:
is_constructible_v<E, decltype(error())>istrue.-3- Mandates:
Uis a specialization ofexpectedandis_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
Uberemove_cvref_t<invoke_result_t<F, decltype(std::move(.value()**this))>>-6- Constraints:
is_constructible_v<E, decltype(std::move(error()))>istrue.-7- Mandates:
Uis a specialization ofexpectedandis_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
Gberemove_cvref_t<invoke_result_t<F, decltype(error())>>.-10- Constraints:
is_constructible_v<T, decltype(isvalue()**this)>true.-11- Mandates:
Gis a specialization ofexpectedandis_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
Gberemove_cvref_t<invoke_result_t<F, decltype(std::move(error()))>>.-14- Constraints:
is_constructible_v<T, decltype(std::move(isvalue()**this))>true.-15- Mandates:
Gis a specialization ofexpectedandis_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
Uberemove_cv_t<invoke_result_t<F, decltype(.value()**this)>>-18- Constraints:
is_constructible_v<E, decltype(error())>istrue.-19- Mandates:
Uis 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_valmember istrueandvalmember 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
Uberemove_cv_t<invoke_result_t<F, decltype(std::move(.value()**this))>>-22- Constraints:
is_constructible_v<E, decltype(std::move(error()))>istrue.-23- Mandates:
Uis 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_valmember istrueandvalmember 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
Gberemove_cv_t<invoke_result_t<F, decltype(error())>>.-26- Constraints:
is_constructible_v<T, decltype(isvalue()**this)>true.-27- Mandates:
Gis 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_valmember isfalseandunexmember 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
Gberemove_cv_t<invoke_result_t<F, decltype(std::move(error()))>>.-30- Constraints:
is_constructible_v<T, decltype(std::move(isvalue()**this))>true.-31- Mandates:
Gis 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_valmember isfalseandunexmember is direct-non-list-initialized withinvoke(std::forward<F>(f), std::move(error())).