Using std::erase() and std::erase_if()

A guide to the C++20 std::erase() and std::erase_if() functions, which simplify the remove-erase idiom for standard library containers

Ryan McCombe
Published

In our previous lesson, we explored the C++ standard library's removal algorithms and the remove-erase idiom. This pattern is a common technique for purging elements from a container.

As a quick refresher, the remove-erase idiom is a two-step process:

  1. Use an algorithm like std::ranges::remove() or std::ranges::remove_if() to shift all the elements you want to keep to the beginning of the container. These algorithms return a subrange representing the "excess" elements at the end of the container that now need to be erased.
  2. Use this return value alongside container-specific algorithms (such as the erase() member function on a std::vector) to erase the unwanted elements.

Here's what it looks like in practice with std::ranges::remove() and a std::vector:

#include <algorithm>
#include <iostream>
#include <vector>

void PrintVector(const std::vector<int>&){/*...*/} int main() { std::vector Numbers{1, 2, 3, 2, 4, 2, 5}; // Step 1: "Remove" all 2s auto Removed{std::ranges::remove(Numbers, 2)}; std::cout << "After remove: "; PrintVector(Numbers); // Step 2: Actually erase them Numbers.erase(Removed.begin(), Removed.end()); std::cout << "After erase: "; PrintVector(Numbers); }
After remove: 1 3 4 5 4 2 5 
After erase:  1 3 4 5

This is quite clunky. It's verbose, requiring two separate statements to do one conceptual thing. Even worse, it's easy to forget the second step, leaving your container in a valid but potentially confusing state with zombie data lingering at the end.

C++20 has ridden to the rescue with a much simpler, safer, and more expressive solution: std::erase() and std::erase_if().

These functions wrap up the remove-erase idiom into a single, clean function call.

Erasing with std::erase()

The std::erase() function is the simplest way to remove all occurrences of a specific value from a container. It takes two arguments:

  1. A reference to the container you want to modify.
  2. The value to remove.

Let's refactor our previous example to use this modern C++20 approach.

main.cpp (After)

#include <algorithm>
#include <iostream>
#include <vector>

void PrintVector(const std::vector<int>&){/*...*/} int main() { std::vector Numbers{1, 2, 3, 2, 4, 2, 5}; auto Removed{std::ranges::remove(Numbers, 2)}; Numbers.erase(Removed.begin(), Removed.end()); std::erase(Numbers, 2); std::cout << "After erase: "; PrintVector(Numbers); }

The end result for both versions is identical:

After erase:  1 3 4 5

Return Value of std::erase()

The std::erase() function returns the number of elements that were removed. This is a size_type of the container (usually an unsigned integer type like size_t). This can be handy for logging, validation, or further logic.

Let's see an example using a different sequential container - a std::string:

#include <iostream>
#include <string>

int main() {
  std::string Text{
    "Hello, world! It's a wonderful world!"
  };
  std::cout << "Original: " << Text;

  size_t RemovedCount = std::erase(Text, 'w');  

  std::cout << "\nModified: " << Text;
  std::cout << "\nRemoved " << RemovedCount
    << " 'w' characters.\n";
}
Original: Hello, world! It's a wonderful world!
Modified: Hello, orld! It's a onderful orld!
Removed 3 'w' characters.

Erasing with std::erase_if()

Just as std::erase() simplifies remove-erase, std::erase_if() simplifies remove_if-erase. It allows us to remove elements from a container based on a condition, or predicate.

It takes two arguments:

  1. A reference to the container.
  2. A predicate that takes an element from the container and returns true if it should be removed. This predicate is usually provided using a .

Let's say we want to remove all even numbers from a vector. Here's the old way vs. the new way:

Files

main.cpp (Before)
main.cpp (After)
Select a file to view its content

The result is the same, but the std::erase_if() approach is far more elegant.

Final vector: 1 3 5 7

Like std::erase(), std::erase_if() also returns the number of elements it removed. Let's see it in a more complex example, where we're managing a list of players and want to remove those who are inactive.

#include <iostream>
#include <string>
#include <vector>

struct Player {
  std::string Name;
  bool isActive;
};

int main() {
  std::vector<Player> Players{
    {"Alice", true}, {"Bob", false},
    {"Charlie", true}, {"David", false},
    {"Eve", true}
  };

  size_t RemovedCount{
    std::erase_if(
      Players,
      [](const Player& P){ return !P.isActive; }
    )
  };

  std::cout << "Removed " << RemovedCount
    << " inactive players\n";

  std::cout << "Active players:\n";
  for (const auto& p : Players) {
    std::cout << "- " << p.Name << '\n';
  }
}
Removed 2 inactive players
Active players:
- Alice
- Charlie
- Eve

Using std::erase_if() with Sets and Maps

So far, we've focused on sequence containers like std::vector and std::string. What about associative containers like maps and sets?

Erasing from Maps

Before C++20, removing elements from a map (like a std::map or std::unordered_map) based on a condition was a bit tricky. You had to write a manual loop, being very careful with how you handled iterators to avoid invalidating them.

C++20's std::erase_if() overload for maps makes this trivial. Given maps are collections of std::pair objects, the predicate for the map's std::erase_if() implementation also receives a std::pair.

Let's see an example where we remove all entries from a map of high scores if the score is below a certain threshold. This example uses a std::map, but std::erase_if() is also available for std::unordered_map and std::multimap:

#include <iostream>
#include <map>
#include <string>

using Scores = std::map<std::string, int>;

void PrintMap(const Scores&) {/*...*/} int main() { Scores HighScores{ {"Alice", 1500}, {"Bob", 99}, {"Charlie", 1200}, {"David", 75} }; std::cout << "--- Before ---\n"; PrintMap(HighScores); // Erase if the score (the pair's second // element) is < 100 size_t RemovedCount{ std::erase_if( HighScores, [](const auto& item){ auto const& [name, score] = item; return score < 100; } ) }; std::cout << "\n--- After ---\n"; PrintMap(HighScores); std::cout << "Removed " << RemovedCount << " entries.\n"; }
--- Before ---
Alice: 1500
Bob: 99
Charlie: 1200
David: 75

--- After ---
Alice: 1500
Charlie: 1200
Removed 2 entries.

Erasing from Sets

Standard library sets like std::set and std::unordered_set also benefit from the new std::erase_if() approach. In this case, the predicate for a set simply takes the element type rather than a std::pair.

Below, we have a set of strings, and we remove any that are shorter than 4 characters. We're using a std::map but std::erase_if() overloads are also available for std::unordered_set and std::multiset:

#include <iostream>
#include <set>
#include <string>

int main() {
  std::set<std::string> Players{
    "Bob", "Alice", "Kevin", "David"
  };

  std::cout << "Original players: ";
  for (const auto& Player : Players) {
    std::cout << Player << " ";
  }

  size_t RemovedCount{std::erase_if(
    Players,
    [](const std::string& Player){
      return Player.length() < 4;
    }
  )};

  std::cout << "\nRemoved " << RemovedCount
    << " player\n";

  std::cout << "Updated players: ";
  for (const auto& Player : Players) {
    std::cout << Player << " ";
  }
}
Original players: Alice Bob David Kevin
Removed 1 player
Updated players: Alice David Kevin

Summary

The addition of std::erase() and std::erase_if() in C++20 is a quality-of-life improvement for developers. It takes a common, slightly cumbersome, and error-prone idiom and transforms it into a simple, expressive, and safe operation.

Whenever you find yourself needing to remove elements from a standard container, your first thought should be to reach for these new tools. They make your code cleaner, your intent clearer, and your programs more robust.

In this lesson, we've covered:

  • How std::erase() and std::erase_if() simplify the classic remove-erase idiom for sequence containers.
  • The syntax, usage, and return values for these new functions.
  • The significant benefits in code conciseness, readability, and safety.
  • The fact that these new functions offer these benefits with no performance cost.
  • The new std::erase_if() function overloads added to associative containers for equally simple and safe conditional removal.
Next Lesson
Lesson 93 of 128

Replacement Algorithms

An overview of the key C++ standard library algorithms for replacing objects in our containers. We cover replace(), replace_if(), replace_copy(), and replace_copy_if().

Have a question about this lesson?
Answers are generated by AI models and may not have been reviewed for accuracy