Title
optional's spaceship with U with a type derived from optional causes infinite constraint meta-recursion
Status
c++23
Section
[optional.comp.with.t]
Submitter
Ville Voutilainen

Created on 2022-07-25.00:00:00 last changed 13 months ago

Messages

Date: 2022-11-17.00:42:33

Proposed resolution:

This wording is relative to N4910.

[Drafting note: This proposed wording removes the only use of is-optional. ]

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

    […]
    namespace std {
      // [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
      template<class T>
        concept is-derived-from-optional = requires(const T& t) { // exposition only
          []<class U>(const optional<U>&){ }(t);
        };
      […]
      // [optional.comp.with.t], comparison with T
      […]
      template<class T, class U> constexpr bool operator>=(const T&, const optional<U>&);
      template<class T, class U> requires (!is-derived-from-optional<U>) && three_way_comparable_with<T, U>
        constexpr compare_three_way_result_t<T, U>
          operator<=>(const optional<T>&, const U&);
      […]
    }
    
  2. Modify [optional.comp.with.t] as indicated:

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

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

Date: 2022-11-12.00:00:00

[ 2022-11-12 Approved at November 2022 meeting in Kona. Status changed: Voting → WP. ]

Date: 2022-08-15.00:00:00

[ 2022-08-23; Reflector poll ]

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

Date: 2022-07-25.00:00:00

What ends up happening is that the constraints of operator<=>(const optional<T>&, const U&) end up in three_way_comparable_with, and then in partially-ordered-with, and the expressions there end up performing a conversion from U to an optional, and we end up instantiating the same operator<=> again, evaluating its constraints again, until the compiler bails out.

See an online example here.

All implementations end up with infinite meta-recursion.

The solution to the problem is to stop the meta-recursion by constraining the spaceship with U so that U is not publicly and unambiguously derived from a specialization of optional, SFINAEing that candidate out, and letting [optional.relops]/20 perform the comparison instead.

History
Date User Action Args
2023-11-22 15:47:43adminsetstatus: wp -> c++23
2022-11-17 00:42:33adminsetmessages: + msg13059
2022-11-17 00:42:33adminsetstatus: voting -> wp
2022-11-08 03:46:49adminsetstatus: ready -> voting
2022-08-23 15:24:34adminsetmessages: + msg12694
2022-08-23 15:24:34adminsetstatus: new -> ready
2022-07-30 13:28:50adminsetmessages: + msg12664
2022-07-25 00:00:00admincreate