Что сделало i = i++ + 1; законным в C++17?
прежде чем вы начнете кричать неопределенное поведение, это явно перечислены в N4659 (C++17)
i = i++ + 1; // the value of i is incremented
еще в N3337 (C++11)
i = i++ + 1; // the behavior is undefined
что изменилось?
из того, что я могу собрать, из [N4659 основных.старпома]
за исключением отмеченных случаев, оценки операндов отдельных операторов и подвыражений отдельных выражений не имеют последовательности. [...] Значение вычисления операндов оператора упорядочиваются перед вычислением значения результата оператора. Если побочный эффект на ячейку памяти не упорядочен относительно другого побочного эффекта на ту же ячейку памяти или вычисление значения с использованием значения любого объекта в той же ячейке памяти, и они потенциально не совпадают, поведение не определено.
здесь стоимостью определена в [N4659 основной.тип]
для тривиально копируемых типов представление значения представляет собой набор битов в представлении объекта, который определяет стоимостью, который является одним дискретным элементом реализации определенного набора значений
за исключением отмеченных случаев, оценки операндов отдельных операторов и подвыражений отдельных выражений не имеют последовательности. [...] Вычисления значений операндов оператора упорядочиваются перед вычислением значения результата оператора. Если побочный эффект на скалярный объект-это неупорядоченное относительно либо другой побочный эффект в тот же скалярный объект или значение, вычисление, используя значение же скалярный объект, поведение неопределено.
аналогично, значение определяется в [N3337 основных.тип]
для тривиально копируемых типов, представление значения-это набор битов в представлении объекта, который определяет стоимостью, который является одним дискретным элементом реализации определенного набора значений.
они идентичны, за исключением упоминание о параллелизме, который не имеет значения, и с использованием памяти вместо скалярный объект, где
арифметические типы, типы перечисления, типы указателей, указатель на типы членов,
std::nullptr_t
, и CV-квалифицированные версии этих типов в совокупности называются скалярными типами.
что не влияет на пример.
оператор присваивания ( = ) и составные операторы присваивания группируются справа налево. Все они требуют изменяемого значения lvalue в качестве левого операнда и возвращают значение lvalue, ссылающееся на левый операнд. Результатом во всех случаях является битовое поле, если левый операнд является битовым полем. Во всех случаях присваивание выполняется последовательно после вычисления значений правого и левого операндов и перед вычислением значений выражения присваивания. Правый операнд секвенируется перед левым операндом.
оператор присваивания ( = ) и составные операторы присваивания группируются справа налево. Все они требуют изменяемого значения lvalue в качестве левого операнда и возвращают значение lvalue, ссылающееся на левый операнд. Результатом во всех случаях является битовое поле, если левый операнд является битовым полем. Во всех случаях присваивание выполняется последовательно после вычисления значений правого и левого операндов и перед вычислением значений выражения присваивания.
единственная разница в том, что последнее предложение отсутствует в N3337.
последнее предложение, однако, не должно иметь никакого значения в качестве левого операнда i
- это не "другая сторона эффект", ни ", используя значение одного и того же объекта скаляр" как id-выражение является lvalue.
3 ответа:
в C++11 акт "присвоения", т. е. побочный эффект изменения LHS, упорядочивается после расчет стоимости правого операнда. Обратите внимание, что это относительно "слабая" гарантия: она производит упорядочивание только по отношению к расчет стоимости из RHS. Это ничего не говорит о побочные эффекты это может присутствовать в RHS, так как возникновение побочных эффектов не является частью расчет стоимости. Требования C++11 не устанавливает относительной последовательности между актом присвоения и любыми побочными эффектами RHS. Это то, что создает потенциал для УБ.
единственная надежда в этом случае - любые дополнительные гарантии, сделанные конкретными операторами, используемыми в RHS. Если RHS использовал префикс
++
, секвенирование свойств, специфичных для префиксной формы++
спас бы день в этом примере. Но постфикс++
Это другая история: она не дает таких гарантий. В C++11 побочные эффекты=
и постфикс++
в конечном итоге непоследовательными по отношению друг к другу в этом примере. И это UB.в C++17 к спецификации оператора присваивания добавляется дополнительное предложение:
правый операнд секвенируется перед левым операндом.
в комбинации с вышеуказанным он делает для очень сильной гарантии. Это последовательности все это происходит в RHS (включая любые побочные эффекты) до все это происходит в LHS. Поскольку фактическое назначение упорядочено после LHS (и RHS), что дополнительное секвенирование полностью изолирует акт назначения от любых побочных эффектов, присутствующих в RHS. Это более сильное секвенирование-это то, что устраняет вышеупомянутый UB.
(обновлено с учетом комментариев @John Bollinger.)
вы определили новое предложение
правый операнд секвенируется перед левым операндом.
и вы правильно определили, что оценка левого операнда как lvalue не имеет значения. Однако,sequenced before задано транзитивное отношение. Поэтому полный правый операнд (включая постинкремент) равен и последовательность перед назначением. В C++11, только значение вычисление правый операнд был секвенирован перед назначением.
в старых стандартах C++ и в C11 определение текста оператора присваивания заканчивается текстом:
оценки операндов не имеют последовательности.
Это означает, что побочные эффекты в операндах не имеют последовательности и поэтому определенно неопределенное поведение, если они используют одну и ту же переменную.
этот текст был просто удален в C++11, оставив его несколько неоднозначным. Это UB или нет? Это было разъяснено в C++17, где они добавлено:
правый операнд секвенируется перед левым операндом.
в качестве примечания, в еще более старых стандартах, все это было очень ясно, например, из C99:
порядок вычисления операндов не определен. Если сделана попытка изменить результат оператора присваивания или доступ к нему после следующей точки последовательности, поведение не определено.
в основном, в C11 / C++11, они все испортили, когда удалили этот текст.