Доступ к паре после ее перемещения в карту

Если я переместил пару в карту, но вставка не удалась, потому что ключ уже существует, могу ли я безопасно использовать пару позже?

//objects available: map, pair

auto insert_pair = map.insert(std::move(pair));

if (!insert_pair.second)
{
  //can I safely access pair here?
}

Описан ли это в стандарте?

Ответ 1

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

Чтобы понять, почему, прежде всего, укажите, что функция члена insert() определена в терминах emplace() (см. 23.4.4.4/1):

Первая форма эквивалентна return emplace(std::forward<P>(x)). [...]

Пост-условия emplace() в свою очередь указаны как (см. п. 23.2.4, Таблица 102):

Вставляет value_typeобъект t, построенный с std::forward<Args>(args)...если и только если нет элемент в контейнере с ключ, эквивалентный ключу t. Компонент boolвозвращаемая пара true, если и только если вставка место и итератор компонент парных точек к элементу с ключом эквивалентно ключу t.

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

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

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

  • Сначала переместите - постройте пару tmp из вашего аргумента (что означало бы, что вы не можете делать предположения о состоянии вашего аргумента после возвращения функции);
  • Проверьте, присутствует ли ключ на карте;
  • Если нет, выполните необходимые операции, чтобы вставить tmp в контейнер.

Или даже:

  • Проверьте, присутствует ли ключ на карте;
  • Если это так, вставьте новый элемент, перемещенный из вашего аргумента,
  • Если нет, переместите-конструируйте новый объект из своего аргумента и ничего не сделайте с ним.

Пункт 3 выше абсолютно бессмыслен, но формально он не запрещен. Запомните формулировку:

Вставляет a value_typeобъект t, построенный с std::forward<Args>(args)...если и только если нет элемент в контейнере с ключ, эквивалентный ключу t.

Это говорит только о том, что если в контейнере нет элемента с ключом, эквивалентным ключу t, в карту не будет вставлен объект, построенный из t, но, насколько глупо это может звучать, он не говорит, что никакой объект вообще не должен быть перемещен из t: если он не вставлен в карту, это разрешено.

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

Если я могу позволить личным мнением проникнуть, хотя, я считаю, что это недостаток.

Ответ 2

Я не мог найти ничего конкретного, но насколько стандарт распространяется на типы, определенные STL (например, пара):

17.6.5.15: Если не указано иное, такие перемещенные объекты            должны быть помещены в действительное, но неопределенное состояние.

Не удовлетворяя, что "иначе указано", которое я не смог найти для "неудачной" карты:: insert, это означало бы, что по стандарту вы не могли бы использовать пару. В действительности, исходя из чувственных реализаций, я полагаю, что пара осталась бы неизменной, но полагаться на это попало бы в область undefined компилятора/stl-специфического поведения.

Ответ 3

Из таблицы 102 в разделе 23.2.4 Ассоциативные контейнеры стандарта С++ 11 (проект n3337) указано (для выражения a_uniq.insert(t), которое применяется в этом случае):

Требуется: если t является выражением non-const rvalue, value_type должен быть MoveInsertable в X; в противном случае value_type должен быть CopyInsertable в X. Эффекты: Вставляет t тогда и только тогда, когда в контейнере нет элемента с ключом, эквивалентным ключу t. Компонент bool возвращенной пары является истинным тогда и только тогда, когда происходит вставка, и компонент итератора пары указывает на элемент с ключом, эквивалентным ключу t.

Он не делает никаких утверждений о влиянии на t, если вставка не возникает (я не уверен, что это подпадает под неопределенное или определенное по реализации поведение). Я не смог найти какую-либо другую статью, которая давала бы дополнительные разъяснения, поэтому, похоже, стандарт не дает окончательного ответа. Можно было бы переписать опубликованный код только для вызова insert(), если пара отсутствует или просто используется возвращенный итератор.

Ответ 4

Да, вы можете использовать пару. Вставка вернет ссылку на пару, которая уже существует на карте.

Документация находится здесь: http://www.cplusplus.com/reference/map/map/insert/