operator== 的隐式类型转换

Implicit type conversion for operator==

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

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==是模板,要调用模板参数T需要推导。但是在 template argument deduction.

中不会考虑隐式转换(ContainerA<int> -> ArrayConstRef<int>ContainerB<int> -> ArrayConstRef<int>

Type deduction does not consider implicit conversions (other than type adjustments listed above): that's the job for overload resolution, which happens later.

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

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

这可以使用 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;
}