Лучший способ удалить элементы из контейнера, используя его версию ranges

#c #range

#c #диапазон

Вопрос:

Я сталкиваюсь с распространенной проблемой в своем коде, когда я хотел бы удалить только один элемент из перевернутого std::vector после того, как он удовлетворяет предикату. Я понимаю, что есть несколько способов сделать это с помощью ranges-v3, но каждый из способов, которые я придумываю, кажется немного запутанным.

Вот пример целевого вектора v:

std::vector v = { 1, 2, 3, 2, 4 };

Результатом должен быть вектор r:

std::vector r = { 1, 2, 3, 4 };

Это будет сделано путем удаления первых 2 (через лямбда-предикат «is_two»), который обнаруживается при обратном обходе вектора v.

Вот один из способов, как это может выглядеть в цикле vanilla C raw:

 auto is_two = [](int a) { return a == 2; };

for (int i = v.size(); --i >= 0;) {

    if (is_two(v[i])) {

        v.erase(v.begin()   i);
        break;
    }
}

  

Вот мои плохие диапазоны -версия v3:

 namespace rs = ranges;
namespace rv = ranges::view;
namespace ra = ranges::action;

rs::for_each(v | rv::enumerate
               | rv::reverse
               | rv::filter([](auto i_e) { return i_e.second == 2; })
               | rv::take(1),

            [amp;](autoamp; i_e) { v.erase(v.begin()   i_e.first); });
  

В идеале мне интересно, есть ли какое-то решение, которое могло бы выглядеть примерно так:

ra::remove_if(v | rv::reverse, is_two);

Чтобы обобщить, я хотел бы знать, как можно взять контейнер, передать его через некоторые операции ranges::view, а затем удалить элементы в результирующем диапазоне из исходного контейнера.

Комментарии:

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

Ответ №1:

Поскольку, похоже, никто не придумал лучшего подхода, я хотел бы упомянуть для вашей пользы возможность прибегнуть к старому доброму reverse_iterator s.

 vec.erase(std::prev(ranges::find_if(vec.rbegin(), vec.rend(), is_two).base()));
  

По общему признанию, это не очень похоже на ranges,
но, по крайней мере, это работает.

Комментарии:

1. На самом деле это лучше, чем мое решение в моем случае использования, спасибо. Не могли бы вы объяснить, что делает .base() ? Еще раз спасибо.

2. @IanCaburian На странице reverse_iterator cppreference подробно объясняется это. base() возвращает базовый итератор, который отключен на 1 по отношению к фактической позиции, следовательно. prev

Ответ №2:

Основная цель циклов for на основе диапазона — согласованность. Для каждого элемента выполняется одна и та же операция.

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

Когда у вас есть молоток, все выглядит как гвоздь. Не превращайте цикл ranged в обычный.