Removing elements with a specific value or that follow a given predicate from a container is a common task. Before C++20, this was not
straightforward. The way to do it had to depend on the type of your container.
For sequence containers, you would end up following what is called the erase-remove idiom:
- Call
std::remove
or std::remove_if
with, as parameters, the container and the criterion to fulfill
- Call the container member function
erase
on the result
For associative containers, you would have no other option than looping through all the elements by hand.
However, C++20 introduced two new methods: std::erase
(for sequence containers only) and std::erase_if
which erase all
elements equal to a value or that satisfy a given predicate.
This rule raises an issue when std::erase
or std::erase_if
could be used to simplify the code.
Noncompliant code example
void removeZeros(std::vector<int> &v) {
v.erase(std::remove(v.begin(), v.end(), 0), v.end()); // Noncompliant
}
void removeOddNumbers(std::vector<int> &v) {
v.erase(std::remove_if(v.begin(), v.end(), [](auto i) { return i%2 == 0; }), v.end()); // Noncompliant
}
void removeOddNumbers(std::unordered_map<std::string, int> &m) {
auto it = m.begin();
while (it != m.end()) { // Noncompliant
if (it->second % 2 == 0) {
it = m.erase(it);
} else {
++it;
}
}
}
Compliant solution
void removeZeros(std::vector<int> &v) {
std::erase(v, 0);
}
void removeOddNumbers(std::vector<int> &v) {
std::erase_if(v, [](auto i) { return i%2 == 0; });
}
void removeOddNumbers(std::unordered_map<std::string, int> &m) {
std::erase_if(m, [](auto item) { return item.second % 2 == 0; });
}