Почему тройной оператор с запятой оценивает только одно выражение в истинном случае?

В настоящее время я изучаю С++ с книгой С++ Primer, и одно из упражнений в книге:

Объясните, что делает следующее выражение: someValue ? ++x, ++y : --x, --y

Что мы знаем? Мы знаем, что тернарный оператор имеет более высокий приоритет, чем оператор запятой. С бинарными операторами это было довольно легко понять, но с тернарным оператором я немного борюсь. С бинарными операторами "с более высоким приоритетом" означает, что мы можем использовать круглые скобки вокруг выражения с более высоким приоритетом и не будем изменять выполнение.

Для тернарного оператора я бы сделал:

(someValue ? ++x, ++y : --x, --y)

в результате чего получается тот же код, который не помогает мне понять, как компилятор будет группировать код.

Однако из тестирования с помощью компилятора С++ я знаю, что выражение компилируется, и я не знаю, что оператор : мог бы стоять сам по себе. Поэтому компилятор правильно интерпретирует тернарный оператор.

Затем я выполнил программу двумя способами:

#include <iostream>

int main()
{
    bool someValue = true;
    int x = 10, y = 10;

    someValue ? ++x, ++y : --x, --y;

    std::cout << x << " " << y << std::endl;
    return 0;
}

Результаты в:

11 10

В то время как с someValue = false он печатает:

9 9

Почему компилятор С++ генерирует код, который для истинной ветки тройного оператора только увеличивает x, тогда как для ложной ветки тройки он уменьшает как x, так и y?

Я даже дошел до круглых скобок вокруг истинной ветки:

someValue ? (++x, ++y) : --x, --y;

но все равно результат 11 10.

Ответ 1

Как @Rakete сказал в своем превосходном ответе, это сложно. Я хотел бы добавить к этому немного.

Тернарный оператор должен иметь вид:

логическое выражение или выражение ? выражение : присваивание-выражение

Итак, мы имеем следующие отображения:

  • someValue: логическое или выражение
  • ++x, ++y: выражение
  • ??? является выражением-присваиванием --x, --y или только --x?

Фактически это только --x, потому что выражение присваивания не может быть проанализировано как два выражения, разделенные запятой (согласно правилам грамматики С++), поэтому --x, --y нельзя рассматривать как выражение присваивания.

В результате получается тройная (условная) часть выражения:

someValue?++x,++y:--x

Это может помочь для удобочитаемости, чтобы рассмотреть ++x,++y для вычисления как-если в скобках (++x,++y); все заключенное между ? и : будет секвенировано после условного. (Я в скобках их для остальной части сообщения).

и оценивается в следующем порядке:

  • someValue?
  • (++x,++y) или --x (в зависимости от результата bool 1).

Это выражение затем рассматривается как левое подвыражение для оператора запятой, причем правое подвыражение --y, например:

(someValue?(++x,++y):--x), --y;

Это означает, что левая сторона является выражением отбрасываемого значения, что означает, что она определенно оценивается, но затем мы оцениваем правую часть и возвращаем ее.

Итак, что происходит, когда someValue есть true?

  • (someValue?(++x,++y):--x) выполняет и увеличивает x и y как 11 и 11
  • Левое выражение отбрасывается (хотя побочные эффекты приращения остаются)
  • Оценим правую часть оператора запятой: --y, который затем уменьшает y до 10

Чтобы "исправить" поведение, вы можете группировать --x, --y с круглыми скобками, чтобы преобразовать его в первичное выражение, в котором есть допустимая запись для выражения-присваивания *:

someValue?++x,++y:(--x, --y);

* Это довольно забавная длинная цепочка, которая связывает выражение-присваивание с основным выражением:

присваивание-выражение --- (может состоять из) → условное выражение → логическое или выражение → логическое и выражение → инклюзивное-или-выражение → эксклюзивное-или -expression → and-expression → expression-expression → relational-expression → shift-expression → additive-expression → multipicative-expression → pm-expression → cast-expression → унитарное выражение → постфикс-выражение → первичное выражение

Ответ 2

Ничего себе, это сложно.

Компилятор видит ваше выражение как:

(someValue ? (++x, ++y) : --x), --y;

Тернарный оператор нуждается в :, он не может стоять сам по себе в этом контексте, но после него нет причин, по которым запятая должна принадлежать ложному случаю.

Теперь может возникнуть больше смысла, почему вы получаете этот вывод. Если someValue истинно, тогда выполняются ++x, ++y и --y, что неэффективно меняет y, но добавляет один к x.

Если someValue является ложным, то выполняются --x и --y, уменьшая их на единицу.

Ответ 3

Почему компилятор С++ генерирует код, который для истинной ветки тернарного оператора только увеличивает x

Вы неверно истолковали, что произошло. Истинная ветвь увеличивает как x, так и y. Тем не менее, y уменьшается немедленно после этого, безусловно.

Вот как это происходит: поскольку условный оператор имеет более высокий приоритет, чем оператор запятой в С++, компилятор анализирует выражение следующим образом:

   (someValue ? ++x, ++y : --x), (--y);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^  ^^^^^

Обратите внимание на "осиротевший" --y после запятой. Это приводит к уменьшению y, который был первоначально увеличен.

Я даже дошел до круглых скобок вокруг истинной ветки:

someValue ? (++x, ++y) : --x, --y;

Вы были на правильном пути, но вы в скобках указали неправильную ветку: вы можете исправить это, заключая в скобки else-ветку, например:

someValue ? ++x, ++y : (--x, --y);

Демо (отпечатки 11 11)

Ответ 4

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

a ? b++, c++ : d++

рассматривается как:

a ? (b++, c++) : d++

(запятая ведет себя так, как если бы она имела более высокий приоритет). С другой стороны,

a ? b++ : c++, d++

рассматривается как:

(a ? b++ : c++), d++

а тернарный оператор имеет более высокий приоритет.

Ответ 5

Точка, которая была упущена в ответах (хотя и затронута комментариями), заключается в том, что условный оператор неизменно используется (обозначается конструкцией?) в реальном коде как ярлык для назначения одному из двух значений переменной.

Таким образом, больший контекст будет:

whatIreallyWanted = someValue ? ++x, ++y : --x, --y;

Это абсурдно на лице, поэтому преступления многообразны:

  • Язык допускает смешные побочные эффекты в задании.
  • Компилятор не предупредил вас, что вы делали причудливые вещи.
  • В книге, как представляется, основное внимание уделяется "трюкам". Можно только надеяться, что ответ в спине "Что это выражение делает, зависит от странных крайних случаев в надуманном примере для создания побочных эффектов, которые никто не ожидает. Никогда не делайте этого".