Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implicit type conversion for operator==

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?

like image 298
Detheroc Avatar asked Feb 27 '26 05:02

Detheroc


1 Answers

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;
}
like image 172
Angelicos Phosphoros Avatar answered Mar 01 '26 19:03

Angelicos Phosphoros