从哲学上讲,std::equality_comparable_with 的公共引用要求明确编码了在编写异构 operator==(T, U) 时所做的隐式声明以实际表示相等*:有一些常见的超类型“T union U”对于它operator== 是平等的。这个“T union U”实际上并不存在于MyCustomType 和SomeOtherType 的代码中。如果我们通过专门化std::common_reference_t使该类型真正存在,那么我们可以遇到std::equality_comparable_with。
*某些类型使用operator== 来表示等价而不是相等(例如迭代器+哨兵),因此不应该也不符合std::equality_comparable_with。
我们可以使用std::basic_common_reference自定义点来指定代理引用:
类模板basic_common_reference 是一个自定义点,允许用户影响common_reference 对用户定义类型(通常是代理引用)的结果。
为此,我们需要:
-
eq_proxy_ref<T> 类似于 T 的引用。
-
MyCustomType 必须可以隐式转换为 eq_proxy_ref<T>。
-
SomeOtherType 必须可以隐式转换为 eq_proxy_ref<T>。
-
MyCustomType 和 SomeOtherType 的 basic_common_reference 必须返回此 eq_proxy_ref<int>。如果您想避免泄露MyCustomType 的内部结构,eq_proxy_ref<MyCustomProxy> 也可以工作。
-
eq_proxy_ref<T> 之间必须有比较运算符。
-
eq_proxy_ref<T>必须遵守要求的精神。
但请注意,std::common_reference_t 不仅仅用于相等,包括 std::three_way_comparable_with、std::totally_ordered_with 以及一些 Ranges 算法或视图。因此,您的 eq_proxy_ref<T> 实际上应该是您的两种类型的通用引用,而不仅仅是启用平等的机制。
满足这些约束的示例如下:
#include <concepts>
#include <type_traits>
// Assuming you don't own SomeOtherType:
template <typename T>
class MyCustomTypeEqProxy {
template <typename>
friend class MyCustomTypeEqProxy;
private:
T ref_;
public:
template <typename U>
requires std::convertible_to<U, T>
constexpr MyCustomTypeEqProxy(U ref)
: ref_(ref)
{}
constexpr MyCustomTypeEqProxy(const SomeOtherType& rhs)
requires std::convertible_to<const int&, T>
: ref_(rhs.value)
{}
template <typename U>
requires std::equality_comparable_with<T, U>
constexpr bool operator==(const MyCustomTypeEqProxy<U>& rhs) const {
return ref_ == rhs.ref_;
};
};
struct MyCustomType {
int x;
constexpr bool operator==(const MyCustomType& rhs) const = default;
constexpr bool operator==(const SomeOtherType& rhs) const {
return x == rhs.value;
}
friend constexpr bool operator==(const SomeOtherType& lhs, const MyCustomType& rhs) {
return lhs.value == rhs.x;
}
constexpr operator MyCustomTypeEqProxy<int>() const { return MyCustomTypeEqProxy<int>(x); }
};
namespace std {
// May not be needed, but allows the custom proxy reference to expand to common references
// of what we're comparing against.
template <typename T, typename U, template <typename> class TQ, template <typename> class UQ>
struct basic_common_reference<::MyCustomTypeEqProxy<T>, U, TQ, UQ> {
using type = ::MyCustomTypeEqProxy< std::common_reference_t<T, UQ<U>> >;
};
template <typename T, typename U, template <typename> class TQ, template <typename> class UQ>
struct basic_common_reference<T, ::MyCustomTypeEqProxy<U>, TQ, UQ> {
using type = ::MyCustomTypeEqProxy< std::common_reference_t<TQ<T>, U> >;
};
// Tell std::common_reference_t about MyCustomTypeEqProxy
template <template <typename> class LQ, template <typename> class RQ>
struct basic_common_reference<::MyCustomType, ::SomeOtherType, LQ, RQ> {
using type = ::MyCustomTypeEqProxy<int>;
};
template <template <typename> class LQ, template <typename> class RQ>
struct basic_common_reference<::SomeOtherType, ::MyCustomType, LQ, RQ> {
using type = ::MyCustomTypeEqProxy<int>;
};
}
Compiler Explorer link
我怀疑我错过了一些细微差别,但这足以满足std::equality_comparable_with。