Этот вопрос возник во время чтения (ответы) Итак, почему я = ++ я + 1 четко определен в С++ 11?
Я понимаю, что тонкое объяснение состоит в том, что (1) выражение ++i
возвращает lvalue, но +
принимает prvalues как операнды, поэтому необходимо выполнить преобразование от lvalue до prvalue; это включает в себя получение текущего значения этого lvalue (а не более старого значения i
) и поэтому должно быть секвенировано после побочного эффекта от приращения (т.е. обновления i
) (2) LHS присвоение также является значением lvalue, поэтому его оценка стоимости не предполагает выборку текущего значения i
; в то время как вычисление этого значения не зависит от w.r.t. вычисление значения RHS, это не представляет проблемы (3) вычисление значения самого присваивания включает в себя обновление i
(снова), но последовательность после вычисления значения его RHS и, следовательно, после предварительного обновления до i
; нет проблем.
Хорошо, поэтому там нет UB. Теперь мой вопрос заключается в том, что если изменить оператор-ассистент от =
до +=
(или аналогичный оператор).
Выполняет ли оценка выражения
i += ++i + 1
поведение undefined?
Как я вижу, стандарт, кажется, противоречит здесь. Поскольку LHS +=
все еще является lvalue (и его RHS все еще является prvalue), те же рассуждения, что и выше, применяются в отношении (1) и (2); в evalutation операндов на +=
не существует поведения undefined. Что касается (3), то операция составного присваивания +=
(точнее, побочный эффект этой операции, вычисление ее значения, если необходимо, в любом случае, секвенированный после ее побочного эффекта) теперь должны как извлекать текущее значение i
, а затем (очевидно, после него, даже если стандарт не говорит об этом явно, или иначе оценка таких операторов всегда вызывает поведение undefined), добавьте RHS и верните результат обратно в i
. Обе эти операции дали бы поведение undefined, если бы они были не подвержены последовательности w.r.t. побочный эффект ++
, но, как указано выше (побочный эффект ++
секвенирован перед вычислением значения +
, дающий RHS оператора +=
, вычисление значения которого секвенировано до операции этого составного назначения), это не так.
Но, с другой стороны, стандарт также говорит, что E += F
эквивалентен E = E + F
, за исключением того, что (lvalue) E оценивается только один раз. Теперь в нашем примере вычисляется значение i
(что и есть E
здесь), поскольку lvalue не содержит ничего, что должно быть секвентировано w.r.t. другие действия, поэтому делать это один или два раза не имеет значения; наше выражение должно быть строго эквивалентно E = E + F
. Но вот проблема; довольно очевидно, что оценка i = i + (++i + 1)
даст поведение undefined! Что дает? Или это дефект стандарта?
Добавлено.. Я немного изменил свое обсуждение выше, чтобы больше оправдать правильное различие между побочными эффектами и вычислениями значений и использовать "оценку" (как и стандарт) выражения для охватывают оба. Я думаю, что мой основной допрос касается не только того, определено ли поведение в этом примере, но и как нужно прочитать стандарт, чтобы решить это. Примечательно, что если принять эквивалентность от E op= F
до E = E op F
как окончательного авторитета для семантики сложной операции присваивания (в этом случае пример явно имеет UB) или просто как указание на то, что математическая операция задействована в определяя значение, которое нужно назначить (а именно тот, который идентифицирован op
, с преобразованным Lvalue-to-rLL LHS оператора присваивания назначений в качестве левого операнда и его RHS как правый операнд). Последний вариант затрудняет обсуждение UB в этом примере, как я пытался объяснить. Я признаю, что соблазнительно сделать эквивалентность авторитетной (так что составные назначения становятся своего рода примитивами второго класса, смысл которых задается переписанием в терминах первоклассных примитивов, поэтому упрощение определения языка), но там являются довольно сильными аргументами против этого:
-
Эквивалентность не является абсолютной, поскольку исключение "
E
оценивается только один раз". Обратите внимание, что это исключение необходимо для того, чтобы избежать использования, когда оценкаE
включает поведение побочного эффекта undefined, например, в довольно распространенном использованииa[i++] += b;
. Если, по-моему, нет абсолютно эквивалентного переписывания для устранения сложных назначений; используя фиктивный оператор|||
для обозначения неследовательных оценок, можно попытаться определитьE op= F;
(сint
операндами для простоты) как эквивалент{ int& L=E ||| int R=F; L = L + R; }
, но тогда в примере больше нет UB. В любом случае стандарт не дает нам рецепта recwriitng. -
Стандарт не обрабатывает составные назначения как примитивы второго класса, для которых не требуется отдельное определение семантики. Например, в 5.17 (акцент мой)
Оператор присваивания (=) и составные операторы присваивания все группы справа налево. [...] Во всех случаях присвоение упорядочивается после значения вычисление правого и левого операндов и перед вычислением значения выражения присваивания. В отношении неопределенного вызова последовательности функций операция составного присвоения представляет собой единую оценку.
- Если бы намерение заключалось в том, чтобы сложные назначения были просто сокращенными для простых назначений, не было бы причин включать их явно в это описание. Последняя фраза даже прямо противоречит тому, что было бы, если бы эквивалентность считалась авторитетной.
Если кто-то признает, что составные присвоения имеют собственную семантику, то возникает точка, в которой их оценка включает (помимо математической операции) больше, чем просто побочный эффект (присвоение) и оценку стоимости (упорядочивается после присвоения), но также и неназванная операция получения (предыдущего) значения LHS. Обычно это рассматривается под заголовком "преобразование lvalue-to-rvalue", но сделать это здесь трудно оправдать, поскольку нет оператора, который принимает LHS как операнд rvalue (хотя есть один в расширенном "эквивалентная" форма). Именно эта неназванная операция, потенциальная невязкая связь с побочным эффектом ++
вызовет UB, но это неэфферентное отношение нигде не указано явно в стандарте, потому что безымянная операция не является. Трудно обосновать UB, используя операцию, само существование которой только неявно в стандарте.