Title
`std::optional` comparisons: constrain harder
Status
voting
Section
[optional.comp.with.t]
Submitter
Jonathan Wakely

Created on 2024-04-19.00:00:00 last changed yesterday

Messages

Date: 2024-08-21.17:27:00

Proposed resolution:

This wording is relative to N4981.

  1. Modify [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 >= 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`.

Date: 2024-08-15.00:00:00

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

Date: 2024-06-15.00:00:00

[ 2024-06-24; Reflector poll ]

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

Date: 2024-04-22.09:23:50

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, and modified by LWG 3746. 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++.

History
Date User Action Args
2024-11-19 16:09:07adminsetstatus: ready -> voting
2024-08-21 17:27:00adminsetmessages: + msg14335
2024-08-21 17:27:00adminsetstatus: new -> ready
2024-06-24 12:09:57adminsetmessages: + msg14181
2024-04-19 17:15:59adminsetmessages: + msg14068
2024-04-19 00:00:00admincreate