Home > Software engineering >  Failure to select correct operator== with MSVC but not gcc/clang for templated class
Failure to select correct operator== with MSVC but not gcc/clang for templated class

Time:03-20

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