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::value_or return statement is inconsistent with MandatesSection: 22.5.3.7 [optional.observe], 22.5.4.6 [optional.ref.observe], 22.8.6.6 [expected.object.obs] Status: New Submitter: Hewill Kang Opened: 2025-09-06 Last modified: 2025-11-20
Priority: 3
View other active issues in [optional.observe].
View all other issues in [optional.observe].
View all issues with New status.
Discussion:
optional<T>::value_or(U&&) requires is_convertible_v<U&&, T>
to ensure that T can be convert from U when optional has no value.
T by static_cast, which is not checked by
is_convertible_v since it only checks for implicit conversions.
This results in rare cases where Mandates may not be violated, but value_or is ill-formed
(demo):
struct S {
operator int() const;
explicit operator int() = delete;
};
int main() {
std::optional<int>{}.value_or(S{}); // fire
}
It is reasonable to create objects that stick to Mandates. The same goes for expected::value_or.
[2025-10-16; Reflector poll]
Set priority to 3 after reflector poll.
This would need to use val instead of **this if LWG 4015(i)
is accepted.
Previous resolution [SUPERSEDED]:
This wording is relative to N5014.
Modify 22.5.3.7 [optional.observe] as indicated:
template<class U = remove_cv_t<T>> constexpr T value_or(U&& v) const &;-15- Mandates:
-16- Effects: Equivalent to:is_copy_constructible_v<T> && is_convertible_v<U&&, T>istrue.return has_value() ? **this : static_cast<T>(std::forward<U>(v));if (has_value()) return **this; return std::forward<U>(v);template<class U = remove_cv_t<T>> constexpr T value_or(U&& v) &&;-17- Mandates:
-18- Effects: Equivalent to:is_move_constructible_v<T> && is_convertible_v<U&&, T>istrue.return has_value() ? std::move(**this) : static_cast<T>(std::forward<U>(v));if (has_value()) return std::move(**this); return std::forward<U>(v);Modify 22.5.4.6 [optional.ref.observe] as indicated:
template<class U = remove_cv_t<T>> constexpr remove_cv_t<T> value_or(U&& u) const;-8- Let
-9- Mandates:Xberemove_cv_t<T>.is_constructible_v<X, T&> && is_convertible_v<U, X>istrue. -10- Effects: Equivalent to:return has_value() ? *val : static_cast<X>(std::forward<U>(u));if (has_value()) return *val; return std::forward<U>(u);Modify 22.8.6.6 [expected.object.obs] as indicated:
template<class U = remove_cv_t<T>> constexpr T value_or(U&& v) const &;-18- Mandates:
is_copy_constructible_v<T>istrueandis_convertible_v<U, T>istrue.-19- Returns:-?- Effects: Equivalent to:has_value() ? **this : static_cast<T>(std::forward<U>(v)).if (has_value()) return **this; return std::forward<U>(v);template<class U = remove_cv_t<T>> constexpr T value_or(U&& v) &&;-20- Mandates:
is_move_constructible_v<T>istrueandis_convertible_v<U, T>istrue.-21- Returns:-?- Effects: Equivalent to:has_value() ? std::move(**this) : static_cast<T>(std::forward<U>(v)).if (has_value()) return std::move(**this); return std::forward<U>(v);
[2025-11-11; Jonathan provides updated wording]
Rebase after LWG 4015(i) which was approved in Kona. This would also resolve LWG 4281(i).
[2025-11-19; Jonathan provides updated wording]
Incorporate proposed resolution of 3424(i) as well, so that the return type is never cv-qualified.
Proposed resolution:
This wording is relative to the working draft after N5014.
Modify 22.5.3.1 [optional.optional.general] as indicated:
[…] template<class U = remove_cv_t<T>> constexpr remove_cv_t<T> value_or(U&&) const &; template<class U = remove_cv_t<T>> constexpr remove_cv_t<T> value_or(U&&) &&; […]
Modify 22.5.3.7 [optional.observe] as indicated:
template<class U = remove_cv_t<T>> constexpr remove_cv_t<T> value_or(U&& v) const &;-?- Let
Xberemove_cv_t<T>.-15- Mandates:
-16- Effects: Equivalent to:isis_copy_constructible_v<T>is_convertible_v<const T&, X> && is_convertible_v<U&&,TX>true.return has_value() ? val : static_cast<T>(std::forward<U>(v));if (has_value()) return val; return std::forward<U>(v);template<class U = remove_cv_t<T>> constexpr remove_cv_t<T> value_or(U&& v) &&;-?- Let
Xberemove_cv_t<T>.-17- Mandates:
-18- Effects: Equivalent to:isis_move_constructible_v<T>is_convertible_v<T, X> && is_convertible_v<U&&,TX>true.return has_value() ? std::move(val) : static_cast<T>(std::forward<U>(v));if (has_value()) return std::move(val); return std::forward<U>(v);
Modify 22.5.4.6 [optional.ref.observe] as indicated:
constexpr T& value() const;-7- Effects: Equivalent to:
return has_value() ? *val : throw bad_optional_access();if (has_value()) return *val; throw bad_optional_access();template<class U = remove_cv_t<T>> constexpr remove_cv_t<T> value_or(U&& u) const;-8- Let
-9- Mandates:Xberemove_cv_t<T>.isis_constructible_v<T>is_convertible_v<T&, X> && is_convertible_v<U, X>true. -10- Effects: Equivalent to:return has_value() ? *val : static_cast<X>(std::forward<U>(u));if (has_value()) return *val; return std::forward<U>(u);
Modify 22.8.6.1 [expected.object.general] as indicated:
[…] template<class U = remove_cv_t<T>> constexpr remove_cv_t<T> value_or(U&&) const &; template<class U = remove_cv_t<T>> constexpr remove_cv_t<T> value_or(U&&) &&; […]
Modify 22.8.6.6 [expected.object.obs] as indicated:
template<class U = remove_cv_t<T>> constexpr remove_cv_t<T> value_or(U&& v) const &;-?- Let
Xberemove_cv_t<T>.-18- Mandates:
is_copy_constructible_v<T>istrueandis_convertible_v<const T&, X> &&is_convertible_v<U, T>istrue.-19- Returns:-?- Effects: Equivalent to:has_value() ? **this : static_cast<T>(std::forward<U>(v)).if (has_value()) return val; return std::forward<U>(v);template<class U = remove_cv_t<T>> constexpr remove_cv_t<T> value_or(U&& v) &&;-?- Let
Xberemove_cv_t<T>.-20- Mandates:
is_move_constructible_v<T>istrueandis_convertible_v<T, X> &&is_convertible_v<U, T>istrue.-21- Returns:-?- Effects: Equivalent to:has_value() ? std::move(**this) : static_cast<T>(std::forward<U>(v)).if (has_value()) return std::move(val); return std::forward<U>(v);