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
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:
- Use an algorithm like
std::ranges::remove()
orstd::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. - Use this return value alongside container-specific algorithms (such as the
erase()
member function on astd::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:
- A reference to the container you want to modify.
- 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:
- A reference to the container.
- 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
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()
andstd::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.
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()
.