Трехсторонний оператор ‹=› структура возврата с неявной функцией преобразования

Рассмотрим следующий бесполезный код:

struct S{
  constexpr operator int() const { return 0; }
  constexpr auto operator<=>(S) const { return *this; }
};

static_assert(S{} <= S{});

Clang и MSVC принимают этот код, но GCC отклоняет его с сообщением об ошибке:

error: no match for 'operator<=' (operand types are 'S' and 'int')

Какой компилятор правильный? Как operator<= синтезируется из operator<=>?


person 康桓瑋    schedule 17.03.2021    source источник
comment
Я думаю, что возвращение *this из <=> - это проблема. Кстати, странно это делать, могу я спросить, почему вы это написали?   -  person cigien    schedule 17.03.2021
comment
@cigien Если честно, я не знаю, зачем я это сделал. Как я уже сказал, это бесполезная программа. : D   -  person 康桓瑋    schedule 17.03.2021
comment
Ах я вижу. Ну ничего страшного в этом нет :) Интересный вопрос.   -  person cigien    schedule 17.03.2021
comment
На всякий случай, S{} <= 0 компилируется нормально? Или он пытается рекурсивно вызвать operator <=> и терпит неудачу, потому что второй тип операнда int не соответствует S?   -  person Ben Voigt    schedule 17.03.2021
comment
@BenVoigt S{} <= 0 отлично компилируется.   -  person cigien    schedule 17.03.2021
comment
@cigien: Да, похоже, что-то должно быть не так с логикой g ++, так как Переписанные кандидаты для оператора @ не рассматриваются в контексте полученного выражения.   -  person Ben Voigt    schedule 17.03.2021


Ответы (2)


Из [over.match.oper] (3.4.1 и 8):

Для реляционных операторов ([expr.rel]) перезаписанные кандидаты включают всех непереписанных кандидатов для выражения x ‹=› y.

а также

Если перезаписанный operator<=> кандидат выбран разрешением перегрузки для operator @, x @ y интерпретируется как [...] (x <=> y) @ 0 [...] с использованием выбранного перезаписанного operator<=> кандидата. Переписанные кандидаты для operator @ не рассматриваются в контексте полученного выражения.

Таким образом, для выражения S{} <= S{} выбранный оператор будет S::operator<=>(S) const, а выражение будет переписано как (S{} <=> S{}) <= 0. В переписанном выражении типы операндов S и int, для которых будет выбран встроенный operator<=(int, int). Таким образом, в конечном итоге выражение (после преобразования S в int) приведет к 0 <= 0, то есть true.

В заключение Clang и MSVC правы в этом случае, и GCC, похоже, не может интерпретировать (S{} <=> S{}) <= 0 как вызов встроенного оператора (обратите внимание на сообщение об ошибке, читающее operand types are 'S' and 'int'). Если вы измените условие в static_assert на переписанное выражение (S{} <=> S{}) <= 0, тогда все три компилятора примут его.

person IlCapitano    schedule 17.03.2021
comment
Имеет смысл. +1. Правильно ли сформирован return *this из <=>? - person 康桓瑋; 17.03.2021
comment
@ 康 桓 瑋 Я не смог найти никаких ограничений для типа возвращаемого значения operator<=>, так что должно быть. Единственное требование к возвращаемому типу (при его использовании для реляционных операторов) - это то, что сравнение его с нулем должно быть правильным. - person IlCapitano; 17.03.2021

Поддержка C ++ 20 в GCC все еще экспериментальная, поэтому пока она действительно поддерживает трехсторонний оператор, ваш static_assert не работает, потому что другие компиляторы автоматически определяют оператор <= из <=>, в то время как GCC кажется более педантичным в своей интерпретации стандарта, и поскольку у вас нет оператора <= напрямую, компилятор выдает ошибку времени компиляции, потому что он не может найти оператор <=.

Если добавить оператор <=, код работает, например:

struct S{
  constexpr operator int() const { return 0; }
  constexpr auto operator<=>(S) const { return *this; }
  constexpr bool operator<=(S) { return true; }
};

static_assert(S{} <= S{});

Кроме того, если вы измените свое assert на трехсторонний оператор, тест не пройдет на всех компиляторах, например:

struct S{
  constexpr operator int() const { return 0; }
  constexpr auto operator<=>(S) const { return *this; }
};

static_assert(S{} <=> S{});

Кроме того, поскольку ожидается, что трехсторонний оператор по существу вернет отрицательное, нулевое или положительное значение (на самом деле возвращает порядок), возврат *this, скорее всего, преобразует значение во что-то, что Clang и MSVC интерпретируют как значение true для утверждения, в то время как GCC может преобразовывать его в значение false, и, таким образом, утверждение не выполняется.

Если вы измените тип возвращаемого значения на любое отрицательное значение (даже -0) или нулевое значение, утверждение будет передано всем компиляторам, кроме того, если вы измените значение на любое положительное значение выше 0, утверждение не будет выполнено на всех компиляторах.

Вы можете изменить трехсторонний оператор, чтобы преобразовать *this в int, который вызовет operator int и вернет 0, что затем вызовет передачу утверждения, например:

constexpr auto operator<=>(S) const { return static_cast<int>(*this); }

Итак, чтобы ответить прямо:

Какой компилятор правильный?

По моему опыту работы с GCC, он имеет тенденцию очень педантично интерпретировать язык и проявлять осторожность, когда дело доходит до спецификации языка, которая может быть двусмысленной перед лицом странного фрагмента кода (например, вашего ).

С этой целью другие компиляторы могут быть слишком свободными в интерпретации языка или GCC может быть слишком строгим в данном конкретном случае.

В любом случае, даже если этот код бесполезен, любой, кто сталкивается с чем-то вроде этого и нацелен на все 3 компилятора, вероятно, должен попытаться быть максимально педантичным с этим типом кода, хотя это обязательно может нарушить цель кода в этот случай, к сожалению.

person txtechhelp    schedule 17.03.2021
comment
static_assert(S{} <=> S{}) не работает, как и 4 <=> 4, должно быть S{} <=> S{} == 0 упорядочения не должны быть преобразованы в bool (но сопоставимы с 0). - person Jarod42; 17.03.2021
comment
@ Jarod42 да, вы правы в том, что это значение сравнения, возвращающее порядок, но я имел в виду утверждение, которое является значением true / false, по какой-то причине не удалось в GCC при его интерпретации трехстороннего оператора в контексте опубликованного кода. - person txtechhelp; 17.03.2021
comment
static_assert(S{} <=> S{} <= 0); принимается тремя компиляторами. и S{} <= S{} и S{} <=> S{} <= 0 должны быть эквивалентными. - person Jarod42; 17.03.2021
comment
eel.is/c++draft должно быть достаточно хорошим бесплатным справочником. - person Jarod42; 17.03.2021
comment
@ Jarod42 Я согласен с вами; но это не отвечает на вопрос и не дает дополнительной информации относительно почему GCC интерпретирует код именно так, особенно потому, что они прямо заявляют, что поддержка C ++ 20 является экспериментальной. Не пытаюсь быть конфронтационным, тем более что я не имею отношения к GCC, Clang или MSVC, но ваши комментарии, кажется, пытаются утверждать что-то отрицательное ?? - person txtechhelp; 17.03.2021
comment
Я не знаю, какой компилятор правильный или нет (поскольку gcc - единственный, кто не согласен с другими ...). Я думаю, что S{} <=> S{} <= 0 более актуально, чем добавление operator <= или проверка bool(S{}<=>S{}). но на самом деле ни один из них не отвечает на вопрос. Я думаю, что в этом ответе есть несвязанные абзацы (а также не отвечает на OP, хотя некоторые части могут быть хорошим комментарием). - person Jarod42; 17.03.2021
comment
поскольку у вас нет <= operator напрямую, компилятор выдает ошибку времени компиляции, потому что он не может найти оператор <=. Я думаю, что это неправильно, у меня нет <= operator, но у меня есть есть <=> operator, поэтому GCC должен синтезировать за меня, но почему-то не удалось вызвать синтезированный. Заметил, что сообщение об ошибке: no match for 'operator<=' (operand types are 'S' and 'int'), а не no match for 'operator<=' (operand types are 'S' and 'S'). - person 康桓瑋; 17.03.2021