Title
reference_wrapper comparisons are not SFINAE-friendly
Status
new
Section
[refwrap.comparisons]
Submitter
Jonathan Wakely

Created on 2024-04-19.00:00:00 last changed 2 weeks ago

Messages

Date: 2024-04-24.18:59:21

Proposed resolution:

This wording is relative to N4981.

  1. Modify [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 [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()).

Date: 2024-04-22.09:13:54

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.

History
Date User Action Args
2024-04-19 16:34:31adminsetmessages: + msg14066
2024-04-19 00:00:00admincreate