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