Следующий пример отлично компилируется с использованием gcc и clang, но не компилируется в MSVC. Я хотел бы знать, не наткнулся ли я невольно на нестандартную территорию? Если нет, то какой компилятор правильный? И может быть есть обходной путь? Минимальный пример (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 выдает ошибку компиляции C2676: binary '==': 'Base<int>' does not define this operator or a conversion to a type acceptable to the predefined operator
.
clang и gcc вызывают operator==(Base, U)
как и ожидалось.
Интересно, что результат будет таким же, если я удалю все члены в Base
и просто определю его как template <class T> struct Base{};
.
Предыстория: у меня есть другой класс template <class T> Derived : Base<T>
, который не содержит дополнительных данных. Я хотел бы повторно использовать все operator==
без необходимости переопределять их снова для Derived
. Без материала SFINEA сравнение Derived<int>
с int
приводит к неоднозначному вызову оператора, потому что два нижних определения operator==
выводят U
как Derived<int>
(правильно, насколько мне известно). Поэтому моя идея заключалась в том, чтобы отключить их, чтобы заставить компилятор использовать operator==(Base<T> const &, Base<U> const &)
. Но тут я столкнулся с вышеуказанной проблемой.
Кроме того, есть ли обходной путь, кроме определения операторов для всех комбинаций Base
и Derived
?
Нет, это не меняет проблемы. MSVC по-прежнему не может скомпилировать код, в то время как gcc и clang с этим справляются. Во всяком случае, в исходном коде это намеренно не явно.
Я удивлен, что MSVC не компилирует ваш код, что мне кажется совершенно правильным.
Итак... не уверен... но я полагаю, что это ошибка MSVC.
В любом случае... учитывая, что вы также просите обходной путь... Я вижу, что и для MSVC работает SFINAE, если вы включаете/отключаете возвращаемый тип операторов, поэтому я предлагаю вам переписать ваши операторы следующим образом
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;
}
Ниже приведен полный пример компиляции с классом Derived
.
#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;
}
Один из способов остановить неявное преобразование
int
вDerived<int>
— пометить конструктор как явный.