rg45:
MW>>О, нашёл:
R>R>Despite appearances, iterator invalidation doesn't prohibit this — when you call push_back(), the value is valid, so the implementation has to ensure that if it triggers a reallocation, it doesn't accidentally invalidate the element.
R>Это звучит резонно.
R>Я вижу, название принципа "nothing prohibits it" взято в кавычки в сообщении по ссылке. Это наводит на мысль, что где-то в природе существует отдельное описание этого принципа. Интересно было бы увидеть.
Это просто следствие непонимания некоторыми стандартизаторами принципов составления строгих формальных описаний. Неспособность родить ясное и непротиворечивое формальное описание приводит к тому, что под фактическим набором правил языка стандартизаторами подразумется не собственно то, что сказано в стандарте прямым текстом, а некая интерпретация (порой довольно неочевидная) стандарта этими самыми стандартизаторами.
Формально поведение можно считать определённым тогда и только тогда, когда существует набор правил, описывающий какое именно поведение дожно иметь место. Единственное допустимое применение принципа "nothing prohibits it" — это установление факта, что в рассматриваемом случае поведение действительно определено неким существующим набором правил, поскольку данный случай соответствует общим условиям этих правил и не соответвует каким-либо специальным условиям (исключительным случаям), которые могли бы сделать данный набор правил неприменимым к данному случаю.
Что касается v.emplace_back(v[0]), то стандарт не определяет порядок noexcept инициализации нового объекта и инвалидации v[0] в случае реаллокации. А это значит, что теоретически допустима conforming реализация, которая бы делала v[0] невалидным до конструирования вставляемого объекта, и тогда "nothing prohibits it" звучит довольно смешно, т.к. наличие undefined behavior следует собственно из описания (довольно кривого, кстати) семантики операции вставки.
Если инициализация вставляемого объекта потенциально может бросать исключение, то у реализации не остаётся выбора, кроме как сначала пытаться его конструировать, а потом уже заниматься удалением исходной последовательности, потому что в случае выброса исключения стандарт требует сохранения прежнего состояния вектора. Если реализация использует один и тот же код для потенциально бросающей и небросающей инициализации, то, скорее всего, v.emplace_back(v[0]) будет работать "корректно".
Подытожим:
1. Для непустого вектора v при v.size() == v.capacity() вычисление v.emplace_back(v[0]) формально может привести к undefined behavior за счёт инвалидации v[0].
2. Стандатизаторы одновременно не признают такую возможность и не признают наличие дефекта в стандарте, потому что они упоротые. Когда правила вменяемо сформулированы, споров вокруг их правильной трактовки не должно возникать в принципе. Если бы в сообществе математиков теоремы формулировались и доказывались с таким же отношением к формальной логике, мы бы сейчас не имели ничего сложнее доказательства теоремы Пифагора (да и там бы, наверное, не обошлось без ошибок и дефект-репортов).
3. Существует реализация std::vector (MSVC), для которой проблема "некорректного" вычисления v.emplace_back(v[0]) действительно имеет место.
4. Вроде как, в новой MSVC реализацию уже пофиксили:
https://blogs.msdn.microsoft.com/vcblog/2017/02/06/stl-fixes-in-vs-2017-rtm/