Почему после вызова std::remove_if размер контейнера не меняется
Что проверяют
Понимаете ли вы, что алгоритмы стандартной библиотеки работают с парой итераторов и ничего не знают о контейнере, которому эти итераторы принадлежат. Отсюда и поведение std::remove_if, и вся идиома удаления.
Как отвечать
std::remove_if не удаляет элементы, а переупорядочивает диапазон. Он перемещает сохраняемые элементы в начало, сохраняя их относительный порядок, и возвращает итератор на новый логический конец. Всё, что лежит между этим итератором и прежним концом, находится в валидном, но неопределённом состоянии: такие элементы разрешено только присваивать или уничтожать, читать их значения нельзя.
Размер не меняется по простой причине: у алгоритма нет доступа к контейнеру. Пара итераторов не позволяет ни освободить память, ни изменить длину, это умеет только сам контейнер. Поэтому удаление делается в два шага:
v.erase(std::remove_if(v.begin(), v.end(), pred), v.end());Хорошо добавить три уточнения. У списков есть собственный метод remove_if, который действительно удаляет узлы и меняет размер. Для ассоциативных контейнеров идиома не работает вовсе: переставлять элементы там нельзя, удаление делается циклом с erase по итератору, который возвращает следующий. И в современном стандарте есть свободная функция std::erase_if, которая делает то же самое одной строкой.
Где спотыкаются
Вызывают алгоритм, видят прежний size() и решают, что ничего не произошло. Дальше проходят по всему контейнеру в цикле и читают хвост, где лежат объекты после перемещения. Для чисел это выглядит как повтор последних значений, для строк и умных указателей как непонятный мусор или пустые объекты.
Второе: применяют идиому к словарю или множеству, а при ошибке компиляции добавляют приведение типов вместо того, чтобы удалять циклом. Ключ там неизменяем не случайно, перестановка элементов сломала бы порядок и поиск.
Третье: путают, что именно переезжает. В начало диапазона переносятся элементы, которые остаются, а не те, которые удаляются, и предикат отвечает на вопрос «удалить ли», а не «оставить ли». Из-за перепутанного знака в предикате из вектора уходит ровно то, что нужно было сохранить.