4071. reference_wrapper comparisons are not SFINAE-friendly

Section: 22.10.6.6 [refwrap.comparisons] Status: WP Submitter: Jonathan Wakely Opened: 2024-04-19 Last modified: 2024-07-08

Priority: Not Prioritized

View all issues with WP status.

Discussion:

P2944R3 added these hidden friends to reference_wrapper:


   friend constexpr synth-three-way-result<T> operator<=>(reference_wrapper, reference_wrapper);
   friend constexpr synth-three-way-result<T> operator<=>(reference_wrapper, const T&);
   friend constexpr synth-three-way-result<T> operator<=>(reference_wrapper, reference_wrapper<const T>);

These functions are not templates, and so their declarations are ill-formed for any type that does have any comparison operators, e.g.


    struct A { } a;
    std::reference_wrapper<A> r(a);

Instantiating reference_wrapper<A> will instantiate the declarations of the hidden friends, which will attempt to determine the return types of the operator<=> functions. That fails because synth-three-way is constrained and can't be called with arguments of type A.

This can be solved by changing those functions into templates, so they aren't instantiated eagerly, e.g.,


    template<class U = T>
    friend constexpr synth-three-way-result<TU> operator<=>(reference_wrapper, reference_wrapper);
or by giving them a deduced return type (so that it isn't instantiated eagerly) and constraining them to only be callable when valid:

    friend constexpr synth-three-way-result<T>auto operator<=>(reference_wrapper x, reference_wrapper y)
    requires requires (const T t) { synth-three-way(t, t); }
The second alternative is used in the proposed resolution.

In practice the requires-clause can be implemented more simply (and efficiently) by checking the constraints of synth-three-way directly:

    requires (const T t) { { t < t } -> boolean-testable; }
but when specified in prose in a Constraints: element it seems clearer to just use synth-three-way(x.get(), y.get()).

The proposed resolution has been committed to libstdc++'s master branch.

[2024-05-08; Reflector poll]

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

[St. Louis 2024-06-29; Status changed: Voting → WP.]

Proposed resolution:

This wording is relative to N4981.

  1. Modify 22.10.6.1 [refwrap.general] as indicated:

    
       // [refwrap.comparisons], comparisons
       friend constexpr bool operator==(reference_wrapper, reference_wrapper);
       friend constexpr bool operator==(reference_wrapper, const T&);
       friend constexpr bool operator==(reference_wrapper, reference_wrapper<const T>);
    
       friend constexpr synth-three-way-result<T>auto operator<=>(reference_wrapper, reference_wrapper);
       friend constexpr synth-three-way-result<T>auto operator<=>(reference_wrapper, const T&);
       friend constexpr synth-three-way-result<T>auto operator<=>(reference_wrapper, reference_wrapper<const T>);
    
  2. Modify 22.10.6.6 [refwrap.comparisons] as indicated:

    
    friend constexpr synth-three-way-result<T>auto operator<=>(reference_wrapper x, reference_wrapper y);
    

    -?- Constraints: The expression synth-three-way(x.get(), y.get()) is well-formed.

    -7- Returns: synth-three-way(x.get(), y.get()).

    
    friend constexpr synth-three-way-result<T>auto operator<=>(reference_wrapper x, const T& y);
    

    -?- Constraints: The expression synth-three-way(x.get(), y) is well-formed.

    -8- Returns: synth-three-way(x.get(), y).

    
    friend constexpr synth-three-way-result<T>auto operator<=>(reference_wrapper x, reference_wrapper<const T> y);
    

    -9- Constraints: is_const_v<T> is false. The expression synth-three-way(x.get(), y.get()) is well-formed.

    -10- Returns: synth-three-way(x.get(), y.get()).