This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.
optional and expected drop the cv-qualification of TSection: 22.5.3.8 [optional.monadic], 22.8.6.7 [expected.object.monadic] Status: New Submitter: Tianjiao Yin Opened: 2026-11-10 Last modified: 2026-11-14
Priority: Not Prioritized
View other active issues in [optional.monadic].
View all other issues in [optional.monadic].
View all issues with New status.
Discussion:
The resolution of LWG 3973(i) (adopted in Kona) changed all occurrences
of value() in 22.5.3.8 [optional.monadic] to *val, and of
**this in 22.8.6.7 [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.
optional to
union { remove_cv_t<T> val; };
following LWG 3891(i) for expected, and changed *val
to val.
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.
operator*() instead of val restores the type that value() had,
is freestanding, and does not perform ADL.
Proposed resolution:
This wording is relative to N5054.
Modify 22.5.3.8 [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
-2- Mandates:Ubeinvoke_result_t<F, decltype(.(val)operator*())>remove_cvref_t<U>is a specialization ofoptional. -3- Effects: Equivalent to: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
-5- Mandates:Ubeinvoke_result_t<F, decltype(std::move(.valoperator*()))>remove_cvref_t<U>is a specialization ofoptional. -6- Effects: Equivalent to: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
-8- Mandates:Uberemove_cv_t<invoke_result_t<F, decltype(.(val)operator*())>>Uis a valid contained type foroptional. The declarationU u(invoke(std::forward<F>(f),valoperator*()));is well-formed for some invented variable
[Note 1: There is no requirement thatu.Uis movable (9.5.1 [dcl.init.general]). — end note] -9- Returns: If*thiscontains a value, anoptional<U>object whose contained value is direct-non-list-initialized withinvoke(std::forward<F>(f),; otherwise,valoperator*())optional<U>().template<class F> constexpr auto transform(F&& f) &&; template<class F> constexpr auto transform(F&& f) const &&;-10- Let
-11- Mandates:Uberemove_cv_t<invoke_result_t<F, decltype(std::move(.valoperator*()))>>Uis a valid contained type foroptional. The declarationU u(invoke(std::forward<F>(f), std::move(valoperator*())));is well-formed for some invented variable
[Note 2: There is no requirement that U is movable (9.5.1 [dcl.init.general]). — end note] -12- Returns: Ifu.*thiscontains a value, anoptional<U>object whose contained value is direct-non-list- initialized withinvoke(std::forward<F>(f), std::move(; otherwise,valoperator*()))optional<U>().
Modify 22.8.6.7 [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
-2- Constraints:Uberemove_cvref_t<invoke_result_t<F, decltype(.(val)operator*())>>is_constructible_v<E, decltype(error())>istrue. -3- Mandates:Uis a specialization ofexpectedandis_same_v<typename U::error_type, E>istrue. -4- Effects: Equivalent to: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
-6- Constraints:Uberemove_cvref_t<invoke_result_t<F, decltype(std::move(.valoperator*()))>>is_constructible_v<E, decltype(std::move(error()))>istrue. -7- Mandates:Uis a specialization ofexpectedandis_same_v<typename U::error_type, E>istrue. -8- Effects: Equivalent to: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
-10- Constraints:Gberemove_cvref_t<invoke_result_t<F, decltype(error())>>.is_constructible_v<T, decltype(is(val)operator*())>true. -11- Mandates:Gis a specialization ofexpectedandis_same_v<typename G::value_type, T>istrue. -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
-14- Constraints:Gberemove_cvref_t<invoke_result_t<F, decltype(std::move(error()))>>.is_constructible_v<T, decltype(std::move(isvaloperator*()))>true. -15- Mandates:Gis a specialization ofexpectedandis_same_v<typename G::value_type, T>istrue. -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
-18- Constraints:Uberemove_cv_t<invoke_result_t<F, decltype(.(val)operator*())>>is_constructible_v<E, decltype(error())>istrue. -19- Mandates:Uis a valid value type forexpected. Ifis_void_v<U>isfalse, the declarationU u(invoke(std::forward<F>(f),valoperator*()));is well-formed.
-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),.valoperator*())(20.3) — Otherwise, evaluates
invoke(std::forward<F>(f),and then returnsvaloperator*())expected<U, E>().template<class F> constexpr auto transform(F&& f) &&; template<class F> constexpr auto transform(F&& f) const &&;-21- Let
-22- Constraints:Uberemove_cv_t<invoke_result_t<F, decltype(std::move(.valoperator*()))>>is_constructible_v<E, decltype(std::move(error()))>istrue. -23- Mandates:Uis a valid value type forexpected. Ifis_void_v<U>isfalse, the declarationU u(invoke(std::forward<F>(f), std::move(valoperator*())));is well-formed.
-24- Effects:
(24.1) — If
has_value()isfalse, returnsexpected<U, E>(unexpect, std::move(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(.valoperator*()))(24.3) — Otherwise, evaluates
invoke(std::forward<F>(f), std::move(and then returnsvaloperator*()))expected<U, E>().template<class F> constexpr auto transform_error(F&& f) &; template<class F> constexpr auto transform_error(F&& f) const &;-25- Let
-26- Constraints:Gberemove_cv_t<invoke_result_t<F, decltype(error())>>.is_constructible_v<T, decltype(is(val)operator*())>true. -27- Mandates:Gis a valid template argument forunexpected(22.8.3.1 [expected.un.general]) and the declarationG g(invoke(std::forward<F>(f), error()));is well-formed.
-28- Returns: Ifhas_value()istrue,expected<T, G>(in_place,; otherwise, anvaloperator*())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
-30- Constraints:Gberemove_cv_t<invoke_result_t<F, decltype(std::move(error()))>>.is_constructible_v<T, decltype(std::move(isvaloperator*()))>true. -31- Mandates:Gis a valid template argument forunexpected(22.8.3.1 [expected.un.general]) and the declarationG g(invoke(std::forward<F>(f), std::move(error())));is well-formed.
-32- Returns: Ifhas_value()istrue,expected<T, G>(in_place, std::move(; otherwise, anvaloperator*()))expected<T, G>object whosehas_valmember isfalseandunexmember is direct-non-list-initialized withinvoke(std::forward<F>(f), std::move(error())).