Почему стирание не поддерживает обратные итераторы?

Я только что написал следующий код и был очень удивлен, что он не компилируется:

std::deque<int> container;
// filling the container...
for (auto it = container.rbegin(); it != container.rend(); ++it)
    if (*it == 5)
    {
        container.erase(it);
        break;
    }

Как видите, я хочу удалить последний элемент, соответствующий определенному критерию, если таковой имеется.

Ошибка

нет соответствующей функции для вызова std::deque::erase(std::reverse_iterator...

Сначала я не поверил, что это вызвано обратным итератором, но это действительно так, поскольку замена rbegin/rend на begin/end решает эту проблему.

Итак, 2 вопроса:

  1. Почему это не поддерживается? Является ли это просто одной из тех мелочей, которые комитет по C++ забыл включить в стандарт, или для такой перегрузки отсутствует какое-либо обоснование?
  2. Каков самый элегантный способ сделать то, что я хочу? Я застрял с итерацией по индексу?

person Violet Giraffe    schedule 25.01.2016    source источник
comment
Я думаю, что ваш ответ здесь: stackoverflow.com/questions/1830158/   -  person Martin Broadhurst    schedule 25.01.2016
comment
m_container и container одно и то же?   -  person zdf    schedule 25.01.2016
comment
@ZDF: да, конечно. Фиксированный.   -  person Violet Giraffe    schedule 25.01.2016
comment
@MartinBroadhurst: погуглив по вашему предложению, я нашел эту статью, которая дает некоторое представление, но все еще не объясняет, почему reverse_iterator::base указывает туда, где он это делает :( : drdobbs.com/cpp/three-guidelines-for-efficient-iterator/   -  person Violet Giraffe    schedule 25.01.2016
comment
@Violet Giraffe Вы можете использовать iterator( rev_it.base() ), как уже предлагал СергейА. Если вы планируете использовать его в for, как в вашем примере, это может не сработать, поскольку стирание сделает недействительными все итераторы.   -  person zdf    schedule 25.01.2016
comment
Во имя всего святого, пожалуйста, используйте std::remove_if. Для таких черных задач циклы и break должны быть пережитком прошлого.   -  person screwnut    schedule 25.01.2016
comment
@screwnut remove_if удаляет элементы путем смещения (фактическое количество элементов останется прежним). Я предполагаю, что фактический код должен быть примерно таким: erase( remove_if(....   -  person zdf    schedule 25.01.2016
comment
@Violet - причина в том, что rend() не может вернуть итератор один перед началом, поскольку ничего подобного нет. Поэтому вместо этого он удерживает begin() и корректирует все свои обращения на 1, чтобы компенсировать это.   -  person Bo Persson    schedule 25.01.2016
comment
@screwnut: но в том то и дело - я не могу! Пожалуйста, внимательно прочитайте вопрос. Я всегда использую remove_if везде, где это возможно; Я даже сделал обертку, чтобы упростить использование стирания-удаления.   -  person Violet Giraffe    schedule 25.01.2016
comment
@ZDF: да, это называется идиомой «стереть-удалить».   -  person Violet Giraffe    schedule 25.01.2016
comment
@VioletGiraffe Ты прав, я идиот. Тем не менее, в духе максимально возможного использования <algorithm> я предлагаю использовать erase с результатом find_if(container.rbegin()... (даже если это не уменьшит количество строк).   -  person screwnut    schedule 26.01.2016
comment
@screwnut Цикл for совершенно ясен и понятен всем. Не используйте причудливые новые вещи только ради этого.   -  person Asteroids With Wings    schedule 07.12.2020
comment
@AsteroidsWithWings Вы утверждаете, что слова найти и стереть вас смущают?   -  person screwnut    schedule 07.12.2020
comment
@screwnut Нет, я утверждаю, что вы настаиваете на том, чтобы OP переключился с ясного, простого для понимания кода на что-то более загадочное без какой-либо веской причины. Ваше первое предложение было даже пессимизацией. В чем смысл? в духе максимально возможного использования «алгоритма» Карго-культовое программирование = плохо.   -  person Asteroids With Wings    schedule 07.12.2020
comment
@AsteroidsWithWings Я утверждаю, что использование английских слов для описания ваших намерений в программе гораздо лучше, чем такие ключевые слова, как for и if. Кроме того, спасибо за ваш вклад, и давайте покончим с этим, пожалуйста.   -  person screwnut    schedule 09.12.2020


Ответы (2)


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

Конечно, самый элегантный обходной путь вряд ли будет выразительным:

container.erase((it+1).base());

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

Если вы обнаружите, что часто его используете, вы можете самостоятельно обернуть более общее решение:

template <typename ReverseIterator>
auto AsForwardIterator(ReverseIterator rit)
{
    std::advance(rit, 1);
    return rit.base();
}

Тогда это просто:

container.erase(AsForwardIterator(it));

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

person Asteroids With Wings    schedule 07.12.2020

Я не могу ответить на вопрос «почему», но чтобы ответить на вопрос «как», вы должны вызвать base() на своем итераторе. Он вернет правильный итератор вперед.

При этом помните о связи между обратными и прямыми итераторами. поначалу это может сбивать с толку, но на самом деле довольно просто. Если у вас есть std::vector, содержащий следующие элементы:

1, 2, 3, 4, 5

И у вас есть reverse_iterator rit, который при разыменовании дает вам 3, тогда *(rit.base) будет равно 4. Чтобы понять почему, просто вспомните, что в обычных итераторах begin() разыменовывается, а end() нет. В обратных итераторах свойство должно быть таким же — rbegin() должно быть разыменуемо, а rend() — нет, т. е. должно указывать за начало контейнера.

Поскольку по определению rend.base() совпадает с begin() (поскольку rend может быть построен как reverse_iterator(begin()), единственный способ, которым все вышеперечисленное может выполняться, - это если rend.base() вернет следующий правый элемент к тому, что находится за началом - begin(). Это легко чтобы увидеть, что такая же симметрия выполняется для rend().

person SergeyA    schedule 25.01.2016
comment
Понизил ответ, потому что вы не можете просто передать reverse_iterator::base() методу erase. - person Violet Giraffe; 25.01.2016
comment
@VioletGiraffe, почему? - person SergeyA; 25.01.2016
comment
Потому что это сотрет не тот элемент! Сначала итератор должен быть скорректирован на 1. - person Violet Giraffe; 25.01.2016
comment
@VioletGiraffe, определите неправильно. Это удалит элемент, на который в данный момент указывает ваш обратный итератор. - person SergeyA; 25.01.2016
comment
Нет, см. предложенный ответ. - person Violet Giraffe; 25.01.2016
comment
@VioletGiraffe, да, вам нужно продвинуть его, что ясно, поскольку rbegin не разыменовывается, а begin - да. Я не давал вам код, просто технику. Тем не менее, вы являетесь владельцем своих отрицательных голосов и можете использовать их по своему усмотрению. ) - person SergeyA; 25.01.2016
comment
Ответы SO — это общедоступные артефакты, которые в будущем увидят сотни или даже тысячи людей. И большинство из этих людей скопируют и вставят ответ, а затем будут сбиты с толку, почему он не работает. И нет, мне непонятно, что итератор должен быть продвинутым. На самом деле он настолько непрозрачен, что вместо него я буду использовать цикл на основе индекса, просто чтобы код оставался читабельным. - person Violet Giraffe; 25.01.2016
comment
@VioletGiraffe, точка зрения принята. Я попытался дополнить ответ некоторыми пояснениями. На самом деле это очень просто с обратными итераторами, в них нет тайной магии. - person SergeyA; 25.01.2016
comment
Этот ответ на самом деле не показывает решение и не затрагивает основную часть вопроса. - person Asteroids With Wings; 07.12.2020