Title
`optional::value_or` return statement is inconsistent with Mandates
Status
new
Section
[optional.observe] [optional.ref.observe] [expected.object.obs]
Submitter
Hewill Kang

Created on 2025-09-06.00:00:00 last changed 2 days ago

Messages

Date: 2025-10-10.15:07:03

Proposed resolution:

This wording is relative to N5014.

  1. Modify [optional.observe] as indicated:

    template<class U = remove_cv_t<T>> constexpr T value_or(U&& v) const &;
    

    -15- Mandates: is_copy_constructible_v<T> && is_convertible_v<U&&, T> is `true`.

    -16- Effects: Equivalent to:

    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: is_move_constructible_v<T> && is_convertible_v<U&&, T> is `true`.

    -18- Effects: Equivalent to:

    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);
    
  2. Modify [optional.ref.observe] as indicated:

    template<class U = remove_cv_t<T>> constexpr remove_cv_t<T> value_or(U&& u) const;
    

    -8- Let X be remove_cv_t<T>.

    -9- Mandates: is_constructible_v<X, T&> && is_convertible_v<U, X> is `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);
    
    
  3. Modify [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> is `true` and is_convertible_v<U, T> is `true`.

    -19- Returns: has_value() ? **this : static_cast<T>(std::forward<U>(v)).

    -?- Effects: Equivalent to:

    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> is `true` and is_convertible_v<U, T> is `true`.

    -21- Returns: has_value() ? std::move(**this) : static_cast<T>(std::forward<U>(v)).

    -?- Effects: Equivalent to:

    if (has_value())
      return std::move(**this);
    return std::forward<U>(v);
    
Date: 2025-09-06.00:00:00

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.

However, the return statement explicitly constructs `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`.

Daniel:

This issue has considerable overlap with LWG 4281.

History
Date User Action Args
2025-10-10 15:07:03adminsetmessages: + msg15134
2025-09-06 00:00:00admincreate