4406. optional::value_or return statement is inconsistent with Mandates

Section: 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-10-10

Priority: Not Prioritized

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.

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.

Proposed resolution:

This wording is relative to N5014.

  1. 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: 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 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 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 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> 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);