This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of WP status.

4072. std::optional comparisons: constrain harder

Section: 22.5.8 [optional.comp.with.t] Status: WP Submitter: Jonathan Wakely Opened: 2024-04-19 Last modified: 2024-11-28

Priority: 1

View all other issues in [optional.comp.with.t].

View all issues with WP status.

Discussion:

P2944R3 added constraints to std::optional's comparisons, e.g.


template<class T, class U> constexpr bool operator==(const optional<T>& x, const optional<U>& y);
-1- MandatesConstraints: The expression *x == *y is well-formed and its result is convertible to bool.


template<class T, class U> constexpr bool operator==(const optional<T>& x, const U& v);
-1- MandatesConstraints: The expression *x == v is well-formed and its result is convertible to bool.

But I don't think the constraint on the second one (the "compare with value") is correct. If we try to compare two optionals that can't be compared, such as optional<void*> and optional<int>, then the first overload is not valid due to the new constraints, and so does not participate in overload resolution. But that means we now consider the second overload, but that's ambiguous. We could either use operator==<void*, optional<int>> or we could use operator==<optional<void*>, int> with the arguments reversed (using the C++20 default comparison rules). We never even get as far as checking the new constraints on those overloads, because they're simply ambiguous.

Before P2944R3 overload resolution always would have selected the first overload, for comparing two optionals. But because that is now constrained away, we consider an overload that should never be used for comparing two optionals. The solution is to add an additional constraint to the "compare with value" overloads so that they won't be used when the "value" is really another optional.

A similar change was made to optional's operator<=> by LWG 3566(i), and modified by LWG 3746(i). I haven't analyzed whether we need the modification here too.

The proposed resolution (without is-derived-from-optional) has been implemented and tested in libstdc++.

[2024-06-24; Reflector poll]

Set priority to 1 after reflector poll. LWG 3746(i) changes might be needed here too, i.e, consider types derived from optional, not only optional itself.

[2024-08-21; Move to Ready at LWG telecon]

[Wrocław 2024-11-23; Status changed: Voting → WP.]

Proposed resolution:

This wording is relative to N4981.

  1. Modify 22.5.8 [optional.comp.with.t] as indicated:

    
    template<class T, class U> constexpr bool operator==(const optional<T>& x, const U& v);
    

    -1- Constraints: U is not a specialization of optional. The expression *x == v is well-formed and its result is convertible to bool.

    
    template<class T, class U> constexpr bool operator==(const T& v, const optional<U>& x);
    

    -3- Constraints: T is not a specialization of optional. The expression v == *x is well-formed and its result is convertible to bool.

    
    template<class T, class U> constexpr bool operator!=(const optional<T>& x, const U& v);
    

    -5- Constraints: U is not a specialization of optional. The expression *x != v is well-formed and its result is convertible to bool.

    
    template<class T, class U> constexpr bool operator!=(const T& v, const optional<U>& x);
    

    -7- Constraints: T is not a specialization of optional. The expression v != *x is well-formed and its result is convertible to bool.

    
    template<class T, class U> constexpr bool operator<(const optional<T>& x, const U& v);
    

    -9- Constraints: U is not a specialization of optional. The expression *x < v is well-formed and its result is convertible to bool.

    
    template<class T, class U> constexpr bool operator<(const T& v, const optional<U>& x);
    

    -11- Constraints: T is not a specialization of optional. The expression v < *x is well-formed and its result is convertible to bool.

    
    template<class T, class U> constexpr bool operator>(const optional<T>& x, const U& v);
    

    -13- Constraints: U is not a specialization of optional. The expression *x > v is well-formed and its result is convertible to bool.

    
    template<class T, class U> constexpr bool operator>(const T& v, const optional<U>& x);
    

    -15- Constraints: T is not a specialization of optional. The expression v > *x is well-formed and its result is convertible to bool.

    
    template<class T, class U> constexpr bool operator<=(const optional<T>& x, const U& v);
    

    -17- Constraints: U is not a specialization of optional. The expression *x <= v is well-formed and its result is convertible to bool.

    
    template<class T, class U> constexpr bool operator<=(const T& v, const optional<U>& x);
    

    -19- Constraints: T is not a specialization of optional. The expression v <= *x is well-formed and its result is convertible to bool.

    
    template<class T, class U> constexpr bool operator>=(const optional<T>& x, const U& v);
    

    -21- Constraints: U is not a specialization of optional. The expression *x &gt;= v is well-formed and its result is convertible to bool.

    
    template<class T, class U> constexpr bool operator>=(const T& v, const optional<U>& x);
    

    -23- Constraints: T is not a specialization of optional. The expression v >= *x is well-formed and its result is convertible to bool.