The following example compiles fine using gcc and clang, but fails to compile in MSVC. I would like to know if I have unwittingly stumbled into non-standard territory? If not, which compiler is correct? And is there maybe a workaround? Minimal example (https://godbolt.org/z/PG35hPGMW):
#include <iostream>
#include <type_traits>
template <class T>
struct Base {
Base() = default;
Base(T) {}
static constexpr bool isBase = true;
};
template <class U>
constexpr std::enable_if_t<U::isBase, bool> EnableComparisonWithValue(U const *) {
return false;
}
template <class>
constexpr bool EnableComparisonWithValue(...) {
return true;
}
template <class T, class U>
bool operator==(Base<T> const &, Base<U> const &) {
std::cout << "operator==(Base, Base)" << std::endl;
return true;
}
template <class T, class U,
std::enable_if_t<EnableComparisonWithValue<U>(nullptr), int> = 0>
bool operator==(Base<T> const &, U const &) {
std::cout << "operator==(Base, U)" << std::endl;
return true;
}
template <class U, class T,
std::enable_if_t<EnableComparisonWithValue<U>(nullptr), int> = 0>
bool operator==(U const &, Base<T> const &) {
std::cout << "operator==(U, Base)" << std::endl;
return true;
}
int main() {
Base<int> b1, b2;
b1 == 42; // gcc and clang compile, msvc does not
}
MSVC throws a compilation error C2676: binary '==': 'Base<int>' does not define this operator or a conversion to a type acceptable to the predefined operator
.
clang and gcc call operator==(Base, U)
as expected.
Interestingly, the result is the same if I remove all the members in Base
and just define it as template <class T> struct Base{};
.
Background: I have another class template <class T> Derived : Base<T>
which does not contain additional data. I would like to reuse all the operator==
without having to redefine them again for Derived
. Without the SFINEA stuff, comparing a Derived<int>
with an int
results in an ambiguous operator call, because the bottom two operator==
definitions deduce U
as Derived<int>
(AFAIK correctly). So my idea was do disable them to force the compiler to use operator==(Base<T> const &, Base<U> const &)
. But then I came upon the above problem.
Also, is there maybe a workaround apart from defining the operators for all combinations of Base
and Derived
?
CodePudding user response:
I'm surprised that MSVC doesn't compile your code, that seems to me perfectly correct.
So... not sure... but I suppose that is a MSVC bug.
Anyway... given that you also ask for a workaround... I see that also for MSVC works SFINAE if you enable/disable the return type of the operators, so I propose that you rewrite your operators as follows
template <class T, class U>
std::enable_if_t<EnableComparisonWithValue<U>(nullptr), bool> operator==(Base<T> const &, U const &) {
std::cout << "operator==(Base, U)" << std::endl;
return true;
}
template <class U, class T>
std::enable_if_t<EnableComparisonWithValue<U>(nullptr), bool> operator==(U const &, Base<T> const &) {
std::cout << "operator==(U, Base)" << std::endl;
return true;
}
The following is a full compiling example with also a Derived
class
#include <iostream>
#include <type_traits>
template <class T>
struct Base {
Base() = default;
Base(T) {}
static constexpr bool isBase = true;
};
struct Derived : public Base<int>
{ };
template <class U>
constexpr std::enable_if_t<U::isBase, bool> EnableComparisonWithValue(U const *) {
return false;
}
template <class>
constexpr bool EnableComparisonWithValue(...) {
return true;
}
template <class T, class U>
bool operator==(Base<T> const &, Base<U> const &) {
std::cout << "operator==(Base, Base)" << std::endl;
return true;
}
template <class T, class U>
std::enable_if_t<EnableComparisonWithValue<U>(nullptr), bool> operator==(Base<T> const &, U const &) {
std::cout << "operator==(Base, U)" << std::endl;
return true;
}
template <class U, class T>
std::enable_if_t<EnableComparisonWithValue<U>(nullptr), bool> operator==(U const &, Base<T> const &) {
std::cout << "operator==(U, Base)" << std::endl;
return true;
}
int main() {
Base<int> b1, b2;
Derived d1, d2;
b1 == b2; // Compiles fine
b1 == 42; // gcc and clang compile, msvc does not
d1 == d2;
d1 == b1;
b2 == d2;
d1 == 42;
42 == d2;
}