Фрагмент тестового кода ниже (CHECK
— это макрос из тестовой среды, он отлично работает):
typedef std::map<int, double> M;
typedef M::value_type VT;
const VT v0;
M mm;
CHECK(mm.begin() == mm.insert(v0).first);
дает false
в результате:
ОШИБКА: проверка НЕ верна. ПРОВЕРЬТЕ ( mm.begin() == mm.insert(v0).first )
значения: ПРОВЕРИТЬ( {?} == {?} )
Изменение последней строки на
1)
CHECK(bool(mm.begin() == mm.insert(v0).first));
CHECK(bool(mm.insert(v0).first == mm.begin()));
CHECK(mm.insert(v0).first == mm.begin());
const bool result = mm.insert(v0).first == mm.begin();
CHECK(result);
ничего не менять: результат тот же false
Однако если я сохраню значение результата вставки во временное состояние, все просто сработает:
typedef std::map<int, double> M;
typedef M::value_type VT;
const VT v0;
M mm;
M::iterator it = mm.insert(v0).first;
CHECK(mm.begin() == it);
УСПЕХ: проверка верна ПРОВЕРКА ( mm.begin() == it )
значения: ПРОВЕРИТЬ( {?} == {?} )
Такое поведение происходит только в режиме сборки Release в VS2019 (C++17 или выше) и не происходит ни в одном другом режиме или компиляторе, который я тестировал.
Может кто-нибудь объяснить, почему это нормально или ошибка? При необходимости со стандартными кавычками C++.
P.S.: Чтобы внести ясность. В моем понимании стандарта и C++ результат странный, потому что:
std::map::insert
НЕ делает недействительными итераторы (по стандарту и реализации MS VS). Так что порядок вызовов не является проблемой (однако я попробовал все возможные комбинации от 1 до 4).bool operator(const std::map::iterator &lhs, const std::map::iterator &rhs)
должен продлить срок службы итератора rvalue результата вставки (второй) и std::map::end()
итератора rvalue. Поэтому сравнение должно быть четко определенным.@Yksisarvinen, не могли бы вы объяснить, почему? Оператор == для итераторов должен продлить время жизни итератора rvalue результата вставки (второй) и должен отлично сравниваться с Begin(). Что мне не хватает?
Подумайте о порядке оценки. mm.begin()
изменится после вставки.
Речь идет не о времени жизни, а о том, когда компилятор выполнит любую часть операнда. До C++17 не было указано, будет ли ваша программа сначала вычислять результат mm.begin()
или mm.insert(v0).first
, поэтому результат сравнения не определен. Я думаю, что в C++17 было указано, что левая часть будет выполняться первой, но, ИМХО, все же проще придерживаться эмпирического правила «не читайте и не записывайте один и тот же объект в одном операторе». Облегчает понимание кода без необходимости думать конкретно о порядке.
@Yksisarvinen: В C++17 для ==
ничего не меняется, только @=
См. Порядок оценки
@Jarod42 Джарод42 Спасибо за исправление. Именно поэтому я придерживаюсь старых правил — так легко запутаться, какой оператор получил специальную обработку, а какой нет, поэтому лучше просто избегать изменения объектов в операторах.
@Jarod42 Jarod42, второй (2.) пример должен работать, но не работает?
Во всех случаях mm.begin()
имеет непредсказуемое значение. Может быть mm.end()
, если вычисляется перед вставкой, или итератор относительно вставленного элемента.
Проверка результата вставки rvalue для std::map дает неожиданные результаты. Мне кажется, что результаты ожидаемы, поскольку результат не указан. Это похоже на int i = 0; CHECK(i == ++i);
, что можно сделать в таком порядке int i = 0; int j = i; i = i + 1; CHECK(j == i);
. (Поскольку порядок операций не определен.) Мое практическое правило: не проверяйте, что мутирует в одном и том же операторе. Используйте отдельные утверждения.
То есть неуказанный порядок оценки ==
является проблемой?
«В этом случае C++17 не определяет никаких других правил, кроме C++11 или даже C++03». Но C++11 делает это. До C++11 этот код был бы UB (mm
модифицируется и доступен в том же выражении). После того, как стало чуть лучше, UB нет, но неупорядоченный порядок делает значение непредсказуемым...
@Jarod42 «mm.begin()
изменится после вставки» — подождите, но для std::map::insert() , в частности, ни один итератор не становится недействительным. См. associative.reqmts.general/175 и эту таблицу по cppreference.
@heapunderrun: Перед вставкой mm.begin()
— это mm.end()
; после вставки это «ожидаемый итератор». mm.end()
(значение до вставки) по-прежнему является действительным значением.
@Jarod42 Ну, кажется, я упустил тот очевидный факт, что begin()
сама по себе не итератор, а функция, которая возвращает итератор. Тогда нет противоречия. Извини!
Как сказал Джарод42,
mm.begin()
имеет непредсказуемое значение.
Для разных компиляторов результаты разные:
При проблемах с различными стандартными результатами выполнения C++ см. Вы можете сообщить о проблеме Сообществу разработчиков и опубликовать ссылку в комментарии, чтобы мы могли принять меры.
До C++17 все примеры, кроме последнего, имели неопределенное поведение. C++17 и более поздние версии, я не уверен на 100%, возможно, это действительно так, но я все равно рекомендую никогда не писать такой код.