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

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

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

0 1

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

это поведение указано где-нибудь в стандарте C++? Или порядок оценки неопределено?

результат был получен с помощью G++ 5.2.1.

4 ответов


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

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

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

#include <map>

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

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

мы знаем, что если не указано, что оценки под-выражений не имеют последовательности, это из the проект стандарта C++11 раздел 1.9 выполнение программы он говорит:

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

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

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

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

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

обратите внимание, я освещаю второй интересный пример из N4228 предложение в вопросе имеет ли этот код из раздела 36.3.6 4-го издания "язык программирования C++" четко определенное поведение?.

обновление

это похоже на пересмотренную версию N4228 был принято эволюцией Рабочая группа на последнем заседании РГ21 но бумага(P0145R0) пока не доступна. Таким образом, это больше не может быть неопределенным в C++17.

обновление 2

редакция 3 p0145 сделал это указано и обновления [expr.зад]Р1:

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


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

5.17 назначение и составные операторы присваивания

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

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

1.3.25 неопределенное поведение

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


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

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

 T tmp = y;
 x = tmp;

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


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

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

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

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

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

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