Home > Mobile >  Implicit type conversion for operator==
Implicit type conversion for operator==

Time:11-13

I'd like to have a way to compare different data types that are internally represented by an array (e.g. a string and a vector of chars) using a common array reference type. Consider the following code:

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

The overloaded operator== isn't matched even though the implicit conversion is available. Interestingly, if I removed the explicit keywords, the compiler manages to convert both objects to pointers and do the comparison that way (which I don't want). Why does one implicit conversion work but not the other? Is there a way to make it work?

CodePudding user response:

This can be solved using SFINAE and little changes in code of your classes.

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

Compiles well with GCC 11.2 -std=c 17.

If you can use C 20, it is better to use concepts for this.

Code below compiles with 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;
}

CodePudding user response:

The problem is that operator== is a template, for it to be called the template parameter T needs to be deduced. But implicit conversion (ContainerA<int> -> ArrayConstRef<int> and ContainerB<int> -> ArrayConstRef<int>) won't be considered in template argument deduction.

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

On the other hand, if you specify the template argument to bypass the deduction (in a ugly style), the code works.

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

Another workaround, you might change the parameter type and add a helper like:

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

  • Related