Search code examples
c++templatesc++17operator-overloadingsfinae

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


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?


Solution

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