Почему тройной оператор с запятой оценивает только одно выражение в истинном случае?
В настоящее время я изучаю С++ с книгой С++ 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;
Это абсурдно на лице, поэтому преступления многообразны:
- Язык допускает смешные побочные эффекты в задании.
- Компилятор не предупредил вас, что вы делали причудливые вещи.
- В книге, как представляется, основное внимание уделяется "трюкам". Можно только надеяться, что ответ в спине "Что это выражение делает, зависит от странных крайних случаев в надуманном примере для создания побочных эффектов, которые никто не ожидает. Никогда не делайте этого".