【问题标题】:How can I tell the compiler that MyCustomType is equality_comparable_with SomeOtherType?我如何告诉编译器 MyCustomType 是equal_comparable_with SomeOtherType?
【发布时间】:2021-06-30 18:56:55
【问题描述】:

假设我有一个与SomeOtherType 进行比较的MyCustomType

struct SomeOtherType {
    int value;

    constexpr bool operator==(const SomeOtherType& rhs) const = default;
};

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;
    }
};

这很好,但是 static_assert(std::equality_comparable_with<MyCustomType, SomeOtherType>); 失败了,这意味着我不能在 std::ranges 算法中使用它们进行异构查找:

error: no type named 'type' in 'struct std::common_reference<const MyCustomType&, const SomeOtherType&>'

我明白为什么会失败:我们可能有一个operator==,但我们不满足通用引用要求(另请参阅Does `equality_­comparable_with` need to require `common_reference`?)。但是,我的类型实际上是与SomeOtherType 相当的平等。我怎样才能让编译器相信是这种情况?

【问题讨论】:

  • 为什么不实现const bool operator==(const SomeOtherType&amp; sot) const;
  • @ThomasMatthews 因为我在写这篇文章时不小心混淆了术语。在这种情况下,SomeOtherTypeint。我会过来澄清这一点。

标签: c++ c++20 c++-concepts


【解决方案1】:

从哲学上讲,std::equality_comparable_with 的公共引用要求明确编码了在编写异构 operator==(T, U) 时所做的隐式声明以实际表示相等*:有一些常见的超类型“T union U”对于它operator== 是平等的。这个“T union U”实际上并不存在于MyCustomTypeSomeOtherType 的代码中。如果我们通过专门化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&lt;T&gt; 类似于 T 的引用。
  • MyCustomType 必须可以隐式转换为 eq_proxy_ref&lt;T&gt;
  • SomeOtherType 必须可以隐式转换为 eq_proxy_ref&lt;T&gt;
  • MyCustomTypeSomeOtherTypebasic_common_reference 必须返回此 eq_proxy_ref&lt;int&gt;。如果您想避免泄露MyCustomType 的内部结构,eq_proxy_ref&lt;MyCustomProxy&gt; 也可以工作。
  • eq_proxy_ref&lt;T&gt; 之间必须有比较运算符。
  • eq_proxy_ref&lt;T&gt;必须遵守要求的精神。

但请注意,std::common_reference_t 不仅仅用于相等,包括 std::three_way_comparable_withstd::totally_ordered_with 以及一些 Ranges 算法或视图。因此,您的 eq_proxy_ref&lt;T&gt; 实际上应该是您的两种类型的通用引用,而不仅仅是启用平等的机制。

满足这些约束的示例如下:

#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

【讨论】:

  • 这让我感觉很恶心。 std::common_reference_t 不仅仅是为了平等 AFAIK,所以这样做似乎有问题
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-02-13
  • 1970-01-01
  • 2021-10-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多