Порядок оценки утверждения присваивания в С++

map<int, int> mp;
printf("%d ", mp.size());
mp[10]=mp.size();
printf("%d\n", mp[10]);

Этот код дает ответ, который не очень интуитивно понятен:

0 1

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

Это поведение указано в любом месте на С++-стандарте? Или порядок оценки undefined?

Результат был получен с использованием g++ 5.2.1.

Ответ 1

Да, это покрывается стандартом, и это неуказанное поведение. Этот конкретный случай рассматривается в недавнем предложении стандартов на С++: N4228: уточнение порядка оценки выражений для Idiomatic С++, целью которого является уточнение порядка правил оценки чтобы он был хорошо определен для определенных случаев.

Он описывает эту проблему следующим образом:

Порядок оценки выражений является повторяющейся темой обсуждения в С++ сообщества. В двух словах, учитывая выражение, такое как f (a, b, c), порядок, в котором оцениваются подвыражения f, a, b, c, не указывается стандартом. Если какие-либо два из этих подвыражений могут изменить один и тот же объект без промежуточных точек последовательности, то поведение программы будет undefined. Например, выражение f (i ++, i), где я является целочисленная переменная приводит к поведению undefined, как и v [i] = я ++. Даже если поведение не undefined, результат оценки выражения все равно может быть угадан. Рассматривать следующий фрагмент программы:

#include <map>

int main() {
  std::map<int, int>  m;
  m[0] = m.size(); // #1
}

Что должен выглядеть объект карты m после оценки выражение помечено # 1? {{0, 0}} или {{0, 1}}?

Мы знаем, что если не указано, оценки подвыражений не подвержены последовательности, это из черновик С++ 11 1.9 Выполнение программы, которая гласит:

За исключением тех случаев, когда отмечено, оценки операндов отдельных операторов и подвыражений отдельных выражений не подвержены. [...]

и весь раздел 5.17 Операторы присваивания и составного присваивания [expr.ass] говорят:

[...] Во всех случаях назначение упорядочивается после значения вычисление правого и левого операндов и перед вычислением значения выражения присваивания. [...]

Таким образом, этот раздел не ограничивает порядок оценки, но мы знаем, что это не поведение undefined, так как оба operator [] и size() являются вызовами функций, а раздел 1.9 сообщает нам (выделение мое):

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

Обратите внимание, что я рассмотрю второй интересный пример из предложения N4228 в этом здесь.

Обновить

Кажется, что пересмотренная версия N4228 была принята рабочей группой Evolution на последней встрече WG21, но документ (P0145R0) пока недоступно. Таким образом, это возможно больше не будет неопределенным в С++ 17.

Ответ 2

Из стандарта С++ 11 (выделение мое):

5.17 Операторы присваивания и составного присваивания

1 Оператор присваивания (=) и составные операторы присваивания все группы справа налево. Все они требуют модифицируемого lvalue в качестве своего левого операнда и возвращают lvalue, ссылаясь на левый операнд. Результатом во всех случаях является бит-поле, если левым операндом является бит-поле. Во всех случаях присваивание упорядочивается после вычисления значения правого и левого операндов и перед вычислением значения выражения присваивания.

Независимо от того, оценивается ли левый операнд первым или оценивается правый операнд, язык не задан. Компилятор может свободно выбирать сначала один из операндов. Поскольку конечный результат вашего кода зависит от порядка оценки операндов, я бы сказал, что это неопределенное поведение, а не поведение undefined.

1.3.25 неуказанное поведение

для хорошо сформированной программы и правильных данных, которая зависит от реализации

Ответ 3

Я уверен, что в стандарте не указывается выражение x = y;, для которого порядок x или y оценивается в стандарте С++ (это причина, по которой вы не можете сделать *p++ = *p++ например, потому что p++ не выполняется в определенном порядке).

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

 T tmp = y;
 x = tmp;

(Конечно, в этом конкретном случае можно предположить, что компилятор предпочитает делать operator[] до size(), потому что он может затем сохранить значение непосредственно в результате operator[] вместо того, чтобы держать его во временном месте, чтобы сохранить его позже после того, как operator[] был оценен - ​​но я уверен, что компилятору НЕ НУЖНО делать это в этом порядке)

Ответ 4

Посмотрим, что ваш код разбивается на:

mp.operator[](10).operator=(mp.size());

который в значительной степени рассказывает историю, что в первой части создается запись в 10, а во второй части размер контейнера присваивается целочисленной ссылке в позиции 10.

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

Когда следует вызывать map::size(), до или после map::operator(int const &);?

Никто не знает.