3566. Constraint recursion for operator<=>(optional<T>, U)

Section: 22.5.8 [optional.comp.with.t] Status: C++23 Submitter: Casey Carter Opened: 2021-06-07 Last modified: 2023-11-22

Priority: Not Prioritized

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

View all issues with C++23 status.

Discussion:

The three-way-comparison operator for optionals and non-optionals is defined in [optional.comp.with.t] paragraph 25:

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

-25- Effects: Equivalent to: return bool(x) ? *x <=> v : strong_ordering::less;

Checking three_way_comparable_with in particular requires checking that x < v is well-formed for an lvalue const optional<T> and lvalue const U. x < v can be rewritten as (v <=> x) > 0. If U is a specialization of optional, this overload could be used for v <=> x, but first we must check the constraints…

The straightforward fix for this recursion seems to be to refuse to check three_way_comparable_with<T> when U is a specialization of optional. MSVC has been shipping such a fix; our initial tests for this new operator triggered the recursion.

Previous resolution [SUPERSEDED]:

This wording is relative to N4885.

  1. Modify 22.5.2 [optional.syn], header <optional> synopsis, as indicated:

    […]
    // 22.5.8 [optional.comp.with.t], comparison with T
    […]
    template<class T, class U> constexpr bool operator>=(const optional<T>&, const U&);
    template<class T, class U> constexpr bool operator>=(const T&, const optional<U>&);
    template<class T, three_way_comparable_with<T>class U>
      requires see below
        constexpr compare_three_way_result_t<T, U>
          operator<=>(const optional<T>&, const U&);
    
    […]
    
  2. Modify 22.5.8 [optional.comp.with.t] as indicated:

    template<class T, three_way_comparable_with<T>class U>
      requires (!is-specialization-of<U, optional>) && three_way_comparable_with<T, U>
        constexpr compare_three_way_result_t<T, U>
          operator<=>(const optional<T>&, const U&);
    

    -?- The exposition-only trait template is-specialization-of<A, B> is a constant expression with value true when A denotes a specialization of the class template B, and false otherwise.

    -25- Effects: Equivalent to: return bool(x) ? *x <=> v : strong_ordering::less;

[2021-06-14; Improved proposed wording based on reflector discussion]

[2021-06-23; Reflector poll]

Set status to Tentatively Ready after five votes in favour during reflector poll.

[2021-10-14 Approved at October 2021 virtual plenary. Status changed: Voting → WP.]

Proposed resolution:

This wording is relative to N4885.

  1. Modify 22.5.2 [optional.syn], header <optional> synopsis, as indicated:

    […]
    // 22.5.3 [optional.optional], class template optional
    template<class T>
    class optional;
    
    template<class T> 
      constexpr bool is-optional = false;               // exposition only
    template<class T> 
      constexpr bool is-optional<optional<T>> = true;   // exposition only
    
    […]
    // 22.5.8 [optional.comp.with.t], comparison with T
    […]
    template<class T, class U> constexpr bool operator>=(const optional<T>&, const U&);
    template<class T, class U> constexpr bool operator>=(const T&, const optional<U>&);
    template<class T, three_way_comparable_with<T>class U>
      requires (!is-optional<U>) && three_way_comparable_with<T, U>
        constexpr compare_three_way_result_t<T, U>
          operator<=>(const optional<T>&, const U&);
    
    […]
    
  2. Modify 22.5.8 [optional.comp.with.t] as indicated:

    template<class T, three_way_comparable_with<T>class U>
      requires (!is-optional<U>) && three_way_comparable_with<T, U>
        constexpr compare_three_way_result_t<T, U>
          operator<=>(const optional<T>& x, const U& v);
    

    -25- Effects: Equivalent to: return bool(x) ? *x <=> v : strong_ordering::less;