Ошибка выбора правильного оператора == с MSVC, но не gcc/clang для шаблонного класса

Следующий пример отлично компилируется с использованием 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?

Один из способов остановить неявное преобразование int в Derived<int> — пометить конструктор как явный.

paddy 19.03.2022 14:23

Нет, это не меняет проблемы. MSVC по-прежнему не может скомпилировать код, в то время как gcc и clang с этим справляются. Во всяком случае, в исходном коде это намеренно не явно.

Sedenion 19.03.2022 18:06
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
2
39
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я удивлен, что 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;
}

Другие вопросы по теме