Являются ли множественные мутации в списках инициализаторов неопределенным поведением?

Меня интересуют списки инициализаторов и точки последовательности. Некоторое время назад я читал, что порядок оценки в списках инициализаторов слева направо. Если это так, то должна быть какая-то точка следования между точками оценки, я не прав? Итак, с учетом сказанного, является ли следующий действительный код? Есть ли что-то, что вызывает в нем неопределенное поведение?

int i = 0;

struct S {
    S(...) {} 
    operator int() { return i; }
};

int main() {
    i = S{++i, ++i};
}

Любые и все ответы приветствуются.


person 0x499602D2    schedule 21.01.2013    source источник


Ответы (2)


Да, код действителен и не имеет неопределенного поведения. Выражения в списке инициализаторов оцениваются слева направо и упорядочиваются до вычисления конструктора S. Поэтому ваша программа должна последовательно присваивать значение 2 переменной i.

Цитирование § 8.5.4 стандарта С++:

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

Таким образом, происходит следующее:

  1. ++i оценивается, что дает i = 1 (первый аргумент конструктора S);
  2. ++i оценивается, что дает i = 2 (второй аргумент конструктора S);
  3. конструктор S выполняется;
  4. Оператор преобразования S выполняется, возвращая значение 2;
  5. значение 2 присваивается i (у которого уже было значение 2).

Другой важный параграф Стандарта — § 1.9/15, в котором также упоминаются аналогичные примеры, которые действуют с неопределенным поведением:

i = v[i++]; // the behavior is undefined
i = i++ + 1; // the behavior is undefined

Однако в том же абзаце сказано:

"За исключением отмеченных случаев, оценки операндов отдельных операторов и подвыражений отдельных выражений не упорядочены. [...] При вызове функции (независимо от того, функция является встроенной), каждое вычисление значения и побочный эффект, связанные с любым выражением-аргументом или с постфиксным выражением, обозначающим вызываемую функцию, упорядочены перед выполнением каждого выражения или оператора в теле вызываемой функции."

Поскольку 1) оценка выражений в списке инициализаторов выполняется слева направо, 2) выполнение конструктора S выполняется после оценки всех выражений в списке инициализаторов и 3) присваивание i выполняется последовательно после выполнения конструктора S (и его оператора преобразования), поведение четко определено.

person Andy Prowl    schedule 21.01.2013
comment
Вы в этом уверены? В C++03 i = ++i; (например) является неопределенным поведением, хотя порядок вычисления гарантирован, поскольку i изменяется дважды между точками последовательности. Я не эксперт по C++11 — и я знаю, что C+11 отказался от концепции точек последовательности, — но эти примеры выглядят ужасно похожими на мой взгляд. - person Nemo; 21.01.2013
comment
@Nemo: в С++ 11 по-прежнему есть концепция последовательности, она просто немного сложнее, чем понятие точек последовательности, для учета нескольких потоков. Однако в ответе отсутствует более важное предложение, следующее за цитируемым: то есть каждое вычисление значения, и побочный эффект, связанный с данным предложением инициализатора, выполняется в последовательности перед каждым вычисление значения и побочный эффект, связанный с любым предложением-инициализатором, которое следует за ним в списке, разделенном запятыми, в списке-инициализаторе. Таким образом, код действительно хорошо определен. - person Mike Seymour; 21.01.2013
comment
@Nemo Но разве в моей перегрузке int() нет точки последовательности? Определение поведения? - person 0x499602D2; 21.01.2013
comment
@MikeSeymour: вы уверены, что эта часть предложения имеет отношение к этому? это только объясняет, что оценки выражений упорядочены в списке инициализаторов, но, похоже, не относится к сомнениям Немо относительно левой и правой сторон operator =. Я считаю, что 1.9/15 решает это. я ошибся? - person Andy Prowl; 21.01.2013
comment
@AndyProwl: это предложение определенно имеет отношение к тому, правильно ли определена оценка списка инициализаторов; каждый элемент списка зависит от побочных эффектов предыдущего, поэтому они должны быть упорядочены так же, как и оценка. Здесь нет проблем с присваиванием, поскольку вызовы конструктора и оператора преобразования вводят точки следования. - person Mike Seymour; 21.01.2013
comment
Я все еще думаю, что это неопределенное поведение, потому что оно похоже на i = ++(++i) - person 0x499602D2; 21.01.2013
comment
@MikeSeymour: я понимаю вашу точку зрения, спасибо за разъяснения. я все еще чувствую, что Немо имел в виду задание, поэтому я задавался вопросом, относилась ли часть предложения, которую я пропустил, к ответу на его сомнение. - person Andy Prowl; 21.01.2013
comment
@David: Нет, это не то же самое - между каждым предложением инициализатора и вызовами функций для конструктора и оператора преобразования есть точки последовательности, что означает, что все выражение i = S{++i,++i} хорошо упорядочено; в то время как в i = ++i побочные эффекты не связаны с назначением, что приводит к неопределенному поведению. - person Mike Seymour; 21.01.2013
comment
@Mike: Значит, если f — обычная функция, то i = f(++i) определена? - person Nemo; 21.01.2013
comment
@Nemo: да, ++i и его побочные эффекты располагаются перед вызовом функции и, следовательно, перед присваиванием. - person Mike Seymour; 21.01.2013
comment
@Nemo это также хорошо определено до C ++ 11, хотя порядок оценки не указан. Я создал самостоятельный ответ освещая это до С++ 11, поскольку это было источником разногласий в другом вопросе, и выяснить это для себя было поучительно. - person Shafik Yaghmour; 12.11.2013

Да, действительно, у вас есть случай неопределенного поведения.

Ниже приведены примеры ситуаций, вызывающих неопределенное поведение:

  • Переменная изменяется несколько раз в пределах одной точки следования. В качестве канонического примера часто приводится выражение i=i++, в котором присваивание переменной i и ее приращение выполняются одновременно. Чтобы узнать больше об ошибках такого рода, прочитайте раздел "точки последовательности".
  • Использование переменной перед ее инициализацией. Неопределенное поведение возникает при попытке использовать переменную.
  • Выделение памяти с помощью оператора new [] и последующее освобождение с помощью оператора удаления. Например: T *p = новый T[10]; удалить п;. Правильный код: T *p = new T[10]; удалить [] п;.

ОТРЕДАКТИРОВАНО

Также ваш код S{++i, ++i}; не компилируется для VS2012. Может быть, вы имели в виду S(++i, ++i);? Если вы используете "()", то присутствует неопределенное поведение. В противном случае ваш исходный код неверен.

person Eugene Rourke    schedule 22.01.2013
comment
Здесь нет неопределенного поведения: синтаксис инициализации скобок — обычный C++11, и тот факт, что VS2012 его не поддерживает, является ограничением этой IDE. Когда используется инициализация скобок, выражения оцениваются слева направо в соответствии с абзацем стандарта, который я цитировал в своем ответе. Во-вторых, он не использует переменную до ее инициализации. Переменная является глобальной и инициализирована до 0. Поэтому, если это вы проголосовали за мой ответ, вы можете пересмотреть свое решение :-) Этот ответ неверен для С++ 11. - person Andy Prowl; 22.01.2013
comment
C++11 не поддерживался полностью до VS2015. Инициализация скобок поддерживалась в VS2013, но не в VS2012. - person Michael Petch; 07.04.2016