Suppose one has a class for which one wants to define hidden friends, e.g. heterogeneous comparison operators:
#include <concepts>
template <typename T> struct S;
template <typename C> constexpr bool is_S = false;
template <typename T> constexpr bool is_S<S<T>> = true;
template <typename T>
struct S {
using type = T;
T data;
constexpr S() : data() {}
constexpr explicit S(const T &t) : data(t) {}
template <typename U>
requires
(!is_S<U>) && std::equality_comparable_with<T, U>
friend constexpr bool operator==(const S &s, const U &u) { return s.data == u; }
};
// pre-existing, not hidden friend
template <typename T>
constexpr bool operator==(const S<T> &a, const S<T> &b) { return a.data == b.data; }
The code above seems reasonable, but it doesn't work due to CWG2369's resolution -- which at the moment is only implemented by GCC (>=11). The resolution make the constraint resolution have endless recursion, for instance when using something like this:
template <typename T>
struct wrapper
{
T value;
};
template <typename T, typename U>
requires std::equality_comparable_with <T, U>
constexpr bool operator==(const wrapper<T>& a, const wrapper<U>& b)
{
return a.value == b.value;
}
using SD = S<double>;
static_assert(std::equality_comparable<wrapper<SD>>); // ERROR, recursive constraints
The solution in this case should be to constrain S's operator== in a non-dependent way:
template <typename V, typename U>
requires
is_S<V> && (!is_S<U>) && std::equality_comparable_with<typename V::type, U>
friend constexpr bool operator==(const V &v, const U &u) { return v.data == u; }
But now this does not depend on a specific S specialization any more, and therefore will cause redefinition errors:
S<int> s1;
S<double> s2; // ERROR: redefinition of operator==
(Godbolt for all of the above.)
Am I missing something or the above solution for the recursive constraints is fundamentally incompatible with hidden friends?