3530. BUILTIN-PTR-MEOW should not opt the type out of syntactic checks

Section: 22.10.8.8 [comparisons.three.way], 22.10.9 [range.cmp] Status: C++23 Submitter: Tim Song Opened: 2021-03-04 Last modified: 2023-11-22

Priority: Not Prioritized

View all issues with C++23 status.

Discussion:

The use of BUILTIN-PTR-MEOW for the constrained comparison function objects was needed to disable the semantic requirements on the associated concepts when the comparison resolves to a built-in operator comparing pointers: the comparison object is adding special handling for this case to produce a total order despite the core language saying otherwise, so requiring the built-in operator to then produce a total order as part of the semantic requirements doesn't make sense.

However, because it is specified as a disjunction on the constraint, it means that the comparison function objects are now required to accept types that don't even meet the syntactic requirements of the associated concept. For example, ranges::less requires all six comparison operators (because of totally_ordered_with) to be present … except when operator< on the arguments resolves to a built-in operator comparing pointers, in which case it just requires operator< and operator== (except that the latter isn't even required to be checked — it comes from the use of ranges::equal_to in the precondition of ranges::less). This seems entirely arbitrary.

[2021-03-12; Reflector poll]

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

[2021-06-07 Approved at June 2021 virtual plenary. Status changed: Voting → WP.]

Proposed resolution:

This wording is relative to N4878.

  1. Edit 22.10.8.8 [comparisons.three.way] as indicated:

    -1- In this subclause, BUILTIN-PTR-THREE-WAY(T, U) for types T and U is a boolean constant expression. BUILTIN-PTR-THREE-WAY(T, U) is true if and only if <=> in the expression

    declval<T>() <=> declval<U>()
    

    resolves to a built-in operator comparing pointers.

    struct compare_three_way {
      template<class T, class U>
        requires three_way_comparable_with<T, U> || BUILTIN-PTR-THREE-WAY(T, U)
      constexpr auto operator()(T&& t, U&& u) const;
    
      using is_transparent = unspecified;
    };
    
    template<class T, class U>
      requires three_way_comparable_with<T, U> || BUILTIN-PTR-THREE-WAY(T, U)
    constexpr auto operator()(T&& t, U&& u) const;
    

    -?- Constraints: T and U satisfy three_way_comparable_with.

    -2- Preconditions: If the expression std​::​forward<T>(t) <=> std​::​forward<U>(u) results in a call to a built-in operator <=> comparing pointers of type P, the conversion sequences from both T and U to P are equality-preserving (18.2 [concepts.equality]); otherwise, T and U model three_way_comparable_with.

    -3- Effects:

    1. (3.1) — If the expression std​::​forward<T>(t) <=> std​::​forward<U>(u) results in a call to a built-in operator <=> comparing pointers of type P, returns strong_­ordering​::​less if (the converted value of) t precedes u in the implementation-defined strict total order over pointers (3.27 [defns.order.ptr]), strong_­ordering​::​greater if u precedes t, and otherwise strong_­ordering​::​equal.

    2. (3.2) — Otherwise, equivalent to: return std​::​forward<T>(t) <=> std​::​forward<U>(u);

  2. Edit 22.10.9 [range.cmp] as indicated:

    -1- In this subclause, BUILTIN-PTR-CMP(T, op, U) for types T and U and where op is an equality (7.6.10 [expr.eq]) or relational operator (7.6.9 [expr.rel]) is a boolean constant expression. BUILTIN-PTR-CMP(T, op, U) is true if and only if op in the expression declval<T>() op declval<U>() resolves to a built-in operator comparing pointers.

    struct ranges::equal_to {
      template<class T, class U>
        requires equality_comparable_with<T, U> || BUILTIN-PTR-CMP(T, ==, U)
      constexpr bool operator()(T&& t, U&& u) const;
    
      using is_transparent = unspecified;
    };
    
    template<class T, class U>
      requires equality_comparable_with<T, U> || BUILTIN-PTR-CMP(T, ==, U)
    constexpr bool operator()(T&& t, U&& u) const;
    

    -?- Constraints: T and U satisfy equality_comparable_with.

    -2- Preconditions: If the expression std​::​forward<T>(t) == std​::​forward<U>(u) results in a call to a built-in operator == comparing pointers of type P, the conversion sequences from both T and U to P are equality-preserving (18.2 [concepts.equality]); otherwise, T and U model equality_comparable_with.

    -3- Effects:

    1. (3.1) — If the expression std​::​forward<T>(t) == std​::​forward<U>(u) results in a call to a built-in operator == comparing pointers of type P, returns false if either (the converted value of) t precedes u or u precedes t in the implementation-defined strict total order over pointers (3.27 [defns.order.ptr]) and otherwise true.

    2. (3.2) — Otherwise, equivalent to: return std​::​forward<T>(t) == std​::​forward<U>(u);

    struct ranges::not_equal_to {
        template<class T, class U>
            requires equality_comparable_with<T, U> || BUILTIN-PTR-CMP(T, ==, U)
        constexpr bool operator()(T&& t, U&& u) const;
    
        using is_transparent = unspecified;
    };
    
    template<class T, class U>
    constexpr bool operator()(T&& t, U&& u) const;
    

    -?- Constraints: T and U satisfy equality_comparable_with.

    -4- operator() has effects eEffects: Equivalent to:

    return !ranges::equal_to{}(std::forward<T>(t), std::forward<U>(u));
    
    struct ranges::greater {
      template<class T, class U>
        requires totally_ordered_with<T, U> || BUILTIN-PTR-CMP(T, <, U)
      constexpr bool operator()(T&& t, U&& u) const;
    
      using is_transparent = unspecified;
    };
    
    template<class T, class U>
    constexpr bool operator()(T&& t, U&& u) const;
    

    -?- Constraints: T and U satisfy totally_ordered_with.

    -5- operator() has effects eEffects: Equivalent to:

    return ranges::less{}(std::forward<U>(u), std::forward<T>(t));
    
    struct ranges::less {
      template<class T, class U>
        requires totally_ordered_with<T, U> || BUILTIN-PTR-CMP(T, <, U)
      constexpr bool operator()(T&& t, U&& u) const;
    
      using is_transparent = unspecified;
    };
    
    template<class T, class U>
      requires totally_ordered_with<T, U> || BUILTIN-PTR-CMP(T, <, U)
    constexpr bool operator()(T&& t, U&& u) const;
    

    -?- Constraints: T and U satisfy totally_ordered_with.

    -6- Preconditions: If the expression std​::​forward<T>(t) < std​::​forward<U>(u) results in a call to a built-in operator < comparing pointers of type P, the conversion sequences from both T and U to P are equality-preserving (18.2 [concepts.equality]); otherwise, T and U model totally_ordered_with. For any expressions ET and EU such that decltype((ET)) is T and decltype((EU)) is U, exactly one of ranges::less{}(ET, EU), ranges::less{}(EU, ET), or ranges::equal_to{}(ET, EU) is true.

    -7- Effects:

    1. (7.1) — If the expression std​::​forward<T>(t) < std​::​forward<U>(u) results in a call to a built-in operator < comparing pointers of type P, returns true if (the converted value of) t precedes u in the implementation-defined strict total order over pointers (3.27 [defns.order.ptr]) and otherwise false.

    2. (7.2) — Otherwise, equivalent to: return std​::​forward<T>(t) < std​::​forward<U>(u);

    struct ranges::greater_equal {
      template<class T, class U>
        requires totally_ordered_with<T, U> || BUILTIN-PTR-CMP(T, <, U)
      constexpr bool operator()(T&& t, U&& u) const;
    
      using is_transparent = unspecified;
    };
    
    template<class T, class U>
    constexpr bool operator()(T&& t, U&& u) const;
    

    -?- Constraints: T and U satisfy totally_ordered_with.

    -8- operator() has effects eEffects: Equivalent to:

    return !ranges::less{}(std::forward<T>(t), std::forward<U>(u));
    
    struct ranges::less_equal {
      template<class T, class U>
        requires totally_ordered_with<T, U> || BUILTIN-PTR-CMP(T, <, U)
      constexpr bool operator()(T&& t, U&& u) const;
    
      using is_transparent = unspecified;
    };
    
    template<class T, class U>
    constexpr bool operator()(T&& t, U&& u) const;
    

    -?- Constraints: T and U satisfy totally_ordered_with.

    -9- operator() has effects eEffects: Equivalent to:

    return !ranges::less{}(std::forward<U>(u), std::forward<T>(t));