Индексирует ли новый элемент карты и имеет ли что-то, что читает его, назначенное ему неопределенное поведение или просто неуказанное?

После ответа на этот вопрос возник долгая дискуссия о том, является ли рассматриваемый код неопределенным поведением или нет. Вот код:

std::map<string, size_t> word_count;
word_count["a"] = word_count.count("a") == 0 ? 1 : 2;

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

Также появилась короткая форма:

(x = 0) = (x == 0) ? 1 : 2; //started as
(x = 0) = (y == "a") ? 1 : 2; //changed to

Я утверждал, что это было примерно так:

(x = 0, x) = (x == 0) ? 1 : 2; //comma sequences x, like [] should

В конце концов, я нашел пример, который, похоже, работал для меня:

i = (++i,i++,i); //well-defined per SO:Undefined Behaviour and Sequence Points

Вернемся к оригиналу, я разбил его на соответствующие вызовы функций, чтобы было легче следовать:

operator=(word_count.operator[]("a"), word_count.count("a") == 0 ? 1 : 2);
   ^       inserts element^                        ^reads same element
   |
assigns to element

Утверждалось, что если word_count["a"] не существует, то он будет назначен дважды без промежуточной последовательности. Я лично не понимал, как это могло произойти, если две вещи, которые я считал правдой, на самом деле были:

  1. Когда сторона выбрана для оценки, вся сторона должна быть оценена, прежде чем другая сторона сможет начать.

  2. Такие конструкции, как word_count["a"] = 1, демонстрируют четко определенное поведение даже в том случае, если элемент вставляется, а затем назначается.

Верны ли эти два утверждения? В конечном счете, является ли это на самом деле неопределенным поведением, и если да, то почему второй оператор работает (при условии, что он работает)? Если второе ложно, я полагаю, что все myMap[i]++; в мире будут неправильно сформированы.

Полезная ссылка: Неопределенное поведение и точки последовательности


comment
Связанный вопрос, заданный в контексте заголовка C: stackoverflow.com/questions/13935904/   -  person Pascal Cuoq    schedule 07.04.2013
comment
@PascalCuoq, спасибо, это кажется довольно актуальным. Вопрос в том, верно ли это для C++ (почти наверняка так) и распространяется ли это на создание нового элемента на карте.   -  person chris    schedule 07.04.2013
comment
Кажется, существует множество вызовов функций, которые повсюду вводят точки последовательности. С другой стороны, если результат все еще не определен, каково практическое применение выражения?   -  person Bo Persson    schedule 07.04.2013
comment
@BoPersson, я постарался не отклониться от вопроса в своем ответе. Я дал четко определенный способ сделать это (если только утверждение 2 не окажется ложным). Мне просто было интересно после этого долгого, убивающего разум обсуждения, что он на самом деле делал.   -  person chris    schedule 07.04.2013


Ответы (2)


Поведение не определено, но не определено.

Обратите внимание, что в выражении:

word_count["a"] = word_count.count("a") == 0 ? 1 : 2;
//              ^

Оператор присваивания, помеченный ^, является встроенным оператором присваивания, поскольку operator [] std::map возвращает size_t&.

Согласно параграфу 5.17/1 стандарта С++ 11 о встроенных операторах присваивания:

Оператор присваивания (=) и составные операторы присваивания группируются справа налево. [..] Во всех случаях присваивание выполняется после вычисления значения правого и левого операндов и перед вычислением значения выражения присваивания. По отношению к вызову функции с неопределенной последовательностью операция составного присваивания представляет собой единственную оценку.

Это означает, что во встроенном присваивании, таком как:

a = b

Сначала оцениваются операнды (в неопределенном порядке), затем выполняется присваивание и, наконец, выполняется вычисление значения всего выражения присваивания.

Учитывая исходное выражение:

word_count["a"] = word_count.count("a") == 0 ? 1 : 2;
//              ^

Из-за абзаца, процитированного выше, ни в коем случае нет двух непоследовательных присвоений одному и тому же объекту: назначение, отмеченное ^, всегда будет упорядочено после присвоения, выполненного operator [] ( как часть оценки левого выражения) в случае, если ключ "a" отсутствует в карте.

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

person Andy Prowl    schedule 07.04.2013
comment
Спасибо, вы даже не представляете, насколько мне стало легче после всего этого. - person chris; 07.04.2013
comment
@chris: Рад, что это помогло. Секвенирование и UB все еще время от времени сбивают меня с толку, поэтому, хотя я нахожу приведенный здесь аргумент достаточно убедительным, я никогда не уверен на 100%. Надеюсь, я правильно понял :) - person Andy Prowl; 07.04.2013
comment
@chris: Я снова засомневался: 1.9/15 Если побочный эффект на скалярном объекте не является последовательным относительно другого побочного эффекта на тот же скалярный объект или вычисления значения с использованием значения того же скалярный объект, поведение не определено. Здесь я не уверен, содержит ли левостороннее выражение побочный эффект, изменяющий скалярное значение, которое считывается при оценке правостороннего выражения. - person Andy Prowl; 07.04.2013
comment
Я как-то даже не видел этого уведомления до сих пор, но вау, количество способов, которыми мысли о неопределенном поведении могут вызвать у вас аневризму, просто поразительно. Я не уверен. - person chris; 14.04.2013
comment
@chris: я действительно очень согласен - person Andy Prowl; 14.04.2013

Он не определен, но не неопределен.

word_count.operator[]("a") и word_count.count("a") — это вызовы функций. По стандарту гарантируется, что выполнение функций не будет чередоваться — либо первое полностью упорядочено перед вторым, либо наоборот.

Конкретное определение может варьироваться в зависимости от стандарта, в С++ 11 соответствующий пункт находится в 1.9/15:

Каждое вычисление в вызывающей функции (включая вызовы других функций), которое иначе не упорядочено особым образом до или после выполнения тела вызываемой функции, неопределенно упорядочено по отношению к выполнению вызываемой функции. 9

9) Другими словами, выполнение функций не чередуется друг с другом.

неопределенная последовательность определена в 1.9/13:

Оценки A и B расположены в неопределенной последовательности, когда либо A упорядочивается до B, либо B упорядочивается до A , но не указано, что именно.

Например, оценка:

word_count["a"] = word_count.count("a");

состоит из трех частей:

  1. выполнение word_count.operator[]("a")
  2. исполнение word_count.count("a")
  3. назначение

Пусть < означает «последовательность перед». Цитируемая часть стандарта гарантирует, что либо 1 < 2, либо 2 < 1. Часть, процитированная в ответе @Andy Prowl, также показывает, что и 1 < 3, и 2 < 3. Итак, вариантов всего два:

  • 1 < 2 < 3
  • 2 < 1 < 3

В обоих случаях все в полной последовательности и шансов на УБ нет.

person zch    schedule 07.04.2013
comment
@AndyProwl, лол! Я никогда не видел, чтобы это происходило раньше :) - person chris; 07.04.2013
comment
@chris: Бывает! :) - person Andy Prowl; 07.04.2013