【问题标题】:Implicit type conversion for operator==operator== 的隐式类型转换
【发布时间】:2021-11-12 09:43:59
【问题描述】:

我想有一种方法来比较使用通用数组引用类型的数组内部表示的不同数据类型(例如字符串和字符向量)。考虑以下代码:

template <typename T>
struct ArrayConstRef {
    const T *data;
    size_t length;
};

template <typename T>
bool operator==(ArrayConstRef<T> a, ArrayConstRef<T> b);

template <typename T>
class ContainerA {
    public:
        operator ArrayConstRef<T>() const;
        explicit operator const T *() const;
};

template <typename T>
class ContainerB {
    public:
        operator ArrayConstRef<T>() const;
        explicit operator const T *() const;
};

int main() {
    if (ContainerA<int>() == ContainerB<int>()) // error - no matching operator==
        printf("equals\n");
    return 0;
}

即使隐式转换可用,重载的operator== 也不匹配。有趣的是,如果我删除了 explicit 关键字,编译器会设法将两个对象都转换为指针并以这种方式进行比较(我不希望这样做)。为什么一种隐式转换有效,而另一种无效?有没有办法让它工作?

【问题讨论】:

  • 这是完整的代码吗?
  • 上面的代码实在看不懂。 operator ArrayConstRef&lt;T&gt;() const; 应该做什么?
  • @kiner_shah:这是一个convertion/cast operator。
  • @Jarod42,谢谢,我明白了 :-)

标签: c++ templates operator-overloading implicit-conversion


【解决方案1】:

这可以使用 SFINAE 解决,只需对类的代码进行少量更改。

#include <cstddef>
#include <cstdio>
#include <type_traits>

template <typename T>
struct ArrayConstRef {
    const T *data;
    size_t length;
};

// This is needed to override other template below
// using argument depended lookup
template <typename T>
bool operator==(ArrayConstRef<T> a, ArrayConstRef<T> b){
    /* Provide your implementation */
    return true;
}

template <
    typename Left,
    typename Right,
    // Sfinae trick :^)
    typename = std::enable_if_t<
        std::is_constructible_v<ArrayConstRef<typename Left::ItemType>, const Left&>
     && std::is_constructible_v<ArrayConstRef<typename Right::ItemType>, const Right&>
     && std::is_same_v<typename Left::ItemType, typename Right::ItemType>
    >
>
inline bool operator==(const Left& a, const Right& b){
    using T = typename Left::ItemType;
    return ArrayConstRef<T>(a) == ArrayConstRef<T>(b);
}

template <typename T>
class ContainerA {
    public:
        // Add type of element
        using ItemType = T;
        operator ArrayConstRef<T>() const;
        explicit operator const T *() const;
};

template <typename T>
class ContainerB {
    public:
        // Add type of element
        using ItemType = T;
        operator ArrayConstRef<T>() const;
        explicit operator const T *() const;
};

int main() {
    if (ContainerA<int>() == ContainerB<int>()) // no error :)
        printf("equals\n");
    return 0;
}

与GCC 11.2 -std=c++17 编译良好。

如果你可以使用 C++20,最好使用概念。

下面的代码用GCC 11.2 -std=c++20编译。

#include <cstddef>
#include <cstdio>
#include <type_traits>

template <typename T>
struct ArrayConstRef {
    const T *data;
    size_t length;
};

// This is needed to override other template below
// using argument depended lookup
template <typename T>
bool operator==(ArrayConstRef<T> a, ArrayConstRef<T> b){
    /* Provide your implementation */
    return true;
}

template <typename Container>
concept ConvertibleToArrayConstRef = 
    requires (const Container& a) { 
        ArrayConstRef<typename Container::ItemType>(a);
    };

template <
    ConvertibleToArrayConstRef Left,
    ConvertibleToArrayConstRef Right
>
requires (std::is_same_v<
            typename Left::ItemType,
            typename Right::ItemType>
)
inline bool operator==(const Left& a, const Right& b){
    using T = typename Left::ItemType;
    return ArrayConstRef<T>(a) == ArrayConstRef<T>(b);
}

template <typename T>
class ContainerA {
    public:
        // Add type of element
        using ItemType = T;
        operator ArrayConstRef<T>() const;
        explicit operator const T *() const;
};

template <typename T>
class ContainerB {
    public:
        // Add type of element
        using ItemType = T;
        operator ArrayConstRef<T>() const;
        explicit operator const T *() const;
};

int main() {
    if (ContainerA<int>() == ContainerB<int>())  // no error :)
        printf("equals\n");
    return 0;
}

【讨论】:

  • 我认为enable_if 是一个错字,应该是enable_if_t。最终,我按照en.cppreference.com/w/cpp/types/enable_if 中的建议将typename = std::enable_if&lt; ... &gt; 更改为std::enable_if_t&lt; ... , bool&gt; = true,以便与潜在的其他类似运算符区分开来。我比另一个解决方案更喜欢这个解决方案的是,它甚至适用于非模板类型,例如我在问题中提到的字符串。谢谢。
  • @Detheroc,是的,你是对的,我应该使用enable_if_t。我修正了我的答案。
【解决方案2】:

问题是operator==是一个模板,要调用模板参数T需要推导。但在template argument deduction 中不会考虑隐式转换(ContainerA&lt;int&gt; -> ArrayConstRef&lt;int&gt; 和 ContainerB&lt;int&gt; -> ArrayConstRef&lt;int&gt;)。

类型推导不考虑隐式转换(除了上面列出的类型调整):这是重载解析的工作,稍后会发生。

另一方面,如果您指定模板参数以绕过推导(以丑陋的方式),则代码可以工作。

if (operator==<int>(ContainerA<int>(), ContainerB<int>()))

另一种解决方法,您可以更改参数类型并添加一个帮助器,例如:

template <typename T>
bool equal_impl(ArrayConstRef<T> a, ArrayConstRef<T> b);

template <template <typename> class C1, template <typename> class C2, typename T>
std::enable_if_t<std::is_convertible_v<C1<T>, ArrayConstRef<T>> 
                 && std::is_convertible_v<C2<T>, ArrayConstRef<T>>, 
                 bool>
operator==(C1<T> a, C2<T> b) {
    return equal_impl<T>(a, b);
}

LIVE

【讨论】:

  • 那么这是否意味着无法实现我正在尝试的目标(同时将运算符用作实际运算符而不是函数)?
  • @Detheroc:如果您有bool operator==(ArrayConstRef&lt;int&gt;, ArrayConstRef&lt;int&gt;);,它将按预期工作(但它不再是模板)。 friend 函数允许有这样的函数,但是它只能被 ADL 发现,所以类似的问题。
  • @Jarod42 是的,我的意思是它仍然必须适用于任意类型,而不仅仅是int。所以那是不可能的?
  • @Detheroc 我试过一个,看起来很复杂。添加到答案中。
  • @songyuanyao 很酷,不知道这个。我决定用类似的解决方案接受另一个答案,因为他是第一个并且得分少得多。 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-20
  • 2013-07-09
  • 1970-01-01
  • 1970-01-01
  • 2011-05-08
  • 2012-01-21
相关资源
最近更新 更多