Created on 2026-11-10.00:00:00 last changed yesterday
Proposed resolution:
This wording is relative to N5054.
Modify [optional.monadic] as indicated:
template<class F> constexpr auto and_then(F&& f) &; template<class F> constexpr auto and_then(F&& f) const &;-1- Let `U` be invoke_result_t<F, decltype(
-2- Mandates: remove_cvref_t<U> is a specialization of `optional`. -3- Effects: Equivalent to:(val)operator*())>.if (*this) { return invoke(std::forward<F>(f),valoperator*()); } else { return remove_cvref_t<U>(); }template<class F> constexpr auto and_then(F&& f) &&; template<class F> constexpr auto and_then(F&& f) const &&;-4- Let `U` be invoke_result_t<F, decltype(std::move(
-5- Mandates: remove_cvref_t<U> is a specialization of `optional`. -6- Effects: Equivalent to:valoperator*()))>.if (*this) { return invoke(std::forward<F>(f), std::move(valoperator*())); } else { return remove_cvref_t<U>(); }template<class F> constexpr auto transform(F&& f) &; template<class F> constexpr auto transform(F&& f) const &;-7- Let `U` be remove_cv_t<invoke_result_t<F, decltype(
-8- Mandates: `U` is a valid contained type for `optional`. The declaration(val)operator*())>>.U u(invoke(std::forward<F>(f),valoperator*()));is well-formed for some invented variable `u`.
[Note 1: There is no requirement that `U` is movable ([dcl.init.general]). — end note] -9- Returns: If `*this` contains a value, an optional<U> object whose contained value is direct-non-list-initialized with invoke(std::forward<F>(f),valoperator*()); otherwise, optional<U>().template<class F> constexpr auto transform(F&& f) &&; template<class F> constexpr auto transform(F&& f) const &&;-10- Let `U` be remove_cv_t<invoke_result_t<F, decltype(std::move(
-11- Mandates: `U` is a valid contained type for `optional`. The declarationvaloperator*()))>>.U u(invoke(std::forward<F>(f), std::move(valoperator*())));is well-formed for some invented variable `u`.
[Note 2: There is no requirement that U is movable ([dcl.init.general]). — end note] -12- Returns: If `*this` contains a value, an optional<U> object whose contained value is direct-non-list- initialized with invoke(std::forward<F>(f), std::move(valoperator*())); otherwise, optional<U>().
Modify [expected.object.monadic] as indicated:
template<class F> constexpr auto and_then(F&& f) &; template<class F> constexpr auto and_then(F&& f) const &;-1- Let `U` be remove_cvref_t<invoke_result_t<F, decltype(
-2- Constraints: is_constructible_v<E, decltype(error())> is `true`. -3- Mandates: `U` is a specialization of `expected` and is_same_v<typename U::error_type, E> is `true`. -4- Effects: Equivalent to:(val)operator*())>>.if (has_value()) return invoke(std::forward<F>(f),valoperator*()); 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` be remove_cvref_t<invoke_result_t<F, decltype(std::move(
-6- Constraints: is_constructible_v<E, decltype(std::move(error()))> is `true`. -7- Mandates: `U` is a specialization of `expected` and is_same_v<typename U::error_type, E> is `true`. -8- Effects: Equivalent to:valoperator*()))>>.if (has_value()) return invoke(std::forward<F>(f), std::move(valoperator*())); 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` be remove_cvref_t<invoke_result_t<F, decltype(error())>>.
-10- Constraints: is_constructible_v<T, decltype((val)operator*())> is `true`. -11- Mandates: `G` is a specialization of `expected` and is_same_v<typename G::value_type, T> is `true`. -12- Effects: Equivalent to:if (has_value()) return G(in_place,valoperator*()); 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` be remove_cvref_t<invoke_result_t<F, decltype(std::move(error()))>>.
-14- Constraints: is_constructible_v<T, decltype(std::move(valoperator*()))> is `true`. -15- Mandates: `G` is a specialization of `expected` and is_same_v<typename G::value_type, T> is `true`. -16- Effects: Equivalent to:if (has_value()) return G(in_place, std::move(valoperator*())); 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` be remove_cv_t<invoke_result_t<F, decltype(
-18- Constraints: is_constructible_v<E, decltype(error())> is `true`. -19- Mandates: `U` is a valid value type for `expected`. If is_void_v<U> is `false`, the declaration(val)operator*())>>.U u(invoke(std::forward<F>(f),valoperator*()));is well-formed.
-20- Effects:
(20.1) — If `has_value()` is `false`, returns expected<U, E>(unexpect, error()).
(20.2) — Otherwise, if is_void_v<U> is `false`, returns an expected<U, E> object whose has_val member is `true` and val member is direct-non-list-initialized with invoke(std::forward<F>(f),
valoperator*()).(20.3) — Otherwise, evaluates invoke(std::forward<F>(f),
valoperator*()) and then returns expected<U, E>().template<class F> constexpr auto transform(F&& f) &&; template<class F> constexpr auto transform(F&& f) const &&;-21- Let `U` be remove_cv_t<invoke_result_t<F, decltype(std::move(
-22- Constraints: is_constructible_v<E, decltype(std::move(error()))> is `true`. -23- Mandates: `U` is a valid value type for `expected`. If is_void_v<U> is `false`, the declarationvaloperator*()))>>.U u(invoke(std::forward<F>(f), std::move(valoperator*())));is well-formed.
-24- Effects:
(24.1) — If `has_value()` is `false`, returns expected<U, E>(unexpect, std::move(error())).
(24.2) — Otherwise, if is_void_v<U> is `false`, returns an expected<U, E> object whose has_val member is `true` and val member is direct-non-list-initialized with invoke(std::forward<F>(f), std::move(
valoperator*())).(24.3) — Otherwise, evaluates invoke(std::forward<F>(f), std::move(
valoperator*())) and then returns 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` be remove_cv_t<invoke_result_t<F, decltype(error())>>.
-26- Constraints: is_constructible_v<T, decltype((val)operator*())> is `true`. -27- Mandates: `G` is a valid template argument for `unexpected` ([expected.un.general]) and the declarationG g(invoke(std::forward<F>(f), error()));is well-formed.
-28- Returns: If `has_value()` is `true`, expected<T, G>(in_place,valoperator*()); otherwise, an expected<T, G> object whose has_val member is `false` and unex member is direct-non-list-initialized with invoke(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` be remove_cv_t<invoke_result_t<F, decltype(std::move(error()))>>.
-30- Constraints: is_constructible_v<T, decltype(std::move(valoperator*()))> is `true`. -31- Mandates: `G` is a valid template argument for `unexpected` ([expected.un.general]) and the declarationG g(invoke(std::forward<F>(f), std::move(error())));is well-formed.
-32- Returns: If `has_value()` is `true`, expected<T, G>(in_place, std::move(valoperator*())); otherwise, an expected<T, G> object whose has_val member is `false` and unex member is direct-non-list-initialized with invoke(std::forward<F>(f), std::move(error())).
The resolution of LWG 3973 (adopted in Kona) changed all occurrences of `value()` in [optional.monadic] to *val, and of `**this` in [expected.object.monadic] to val. The intention was not to change the meaning, just avoid the non-freestanding `value()` function, and avoid ADL that would be caused by using `**this`.
LWG 4015 then changed the exposition-only member of `optional` to
union { remove_cv_t<T> val; };
following LWG 3891 for `expected`, and changed *val to val.
However, because val drops the cv-qualifiers of `T`, in the non-const overloads such as and_then(F&&) & the type of val is remove_cv_t<T>&, whereas the type of `value()` was T&. This means the callable can modify a const contained value:
std::optional<const int> o(1);
o.and_then([](int& r) { r = 2; return std::optional<int>(r); }); // *o is now 2
std::expected<const int, int> e(1);
e.transform([](int& r) { return r = 2; }); // *e is now 2
This was ill-formed in C++23. The rvalue overloads have the same problem (std::move(val) is remove_cv_t<T>&& rather than T&&), as does volatile T.
Using `operator*()` instead of val restores the type that `value()` had, is freestanding, and does not perform ADL.| History | |||
|---|---|---|---|
| Date | User | Action | Args |
| 2026-11-14 16:42:20 | admin | set | messages: + msg16654 |
| 2026-11-10 00:00:00 | admin | create | |