20

We can erase one element/ entry from a container by the popular erase–remove idiom. However, many of us would have encountered some problems while applying this idiom:

  • one can easily get into the pitfall of typos like

    c.erase(std::remove_if(c.begin(), c.end(), pred));
    //                                             , c.end() //---> missing here
    

    or

    c.erase((std::remove_if(c.begin(), c.end(), pred), c.end()))
    //      ^^                                               ^^
    // extra () makes it pass only c.end() to the c.erase
    
  • It even follows the wrong semantics for containers like std::list by not selecting its own member std::list::remove_if() for the idiom.
  • Thirdly, using std::remove_if does not work for associative containers.

Do we have anything generalized and less typo-prone than std::erase-std::remove_if or something like std::erase_if within the scope of , or will there be such a utility in ?

JeJo
  • 30,635
  • 6
  • 49
  • 88

1 Answers1

30

Not in the scope of , but onwards!

Yes. The proposal of consistent container erasure has been mentioned in n4009 paper and finally adopted in C++20 standard as std::erase_if which is a non-member function for each containers.

This ensures a uniform container erasure semantics for std::basic_string and all standard containers, except std::array(as it has the fixed-size).

This means that the boilerplate code

container.erase(
    std::remove_if(
        container.begin(), container.end(),
        [](const auto& element) ->bool { return /* condition */; }),
    vec.end());

will simply melt down to a generalized form of

std::erase_if(container, [](const auto& element) ->bool { return /* condition */; });

Secondly, this uniform syntax selects the proper semantics for each container. This means


In addition to that, the standard also added std::erase for sequence containers of the form

std::erase(container, value_to_be_removed);
JeJo
  • 30,635
  • 6
  • 49
  • 88
  • 1
    How do I extend this for my own containers? Will my own `erase_if(...)` be found by ADL? – Justin Jul 02 '19 at 20:24
  • 1
    @Justin if I understood correctly, defining a non-member `erase_if(...)` for the own container wouldn't be enough? maybe putting the scope of `std::` will be adding to namespace std. But later part I am not sure. – JeJo Jul 02 '19 at 20:28
  • 3
    Indeed, from the paper, this utility is specified for each standard container and doesn't use ADL. As adding a custom overload to `std` is not allowed, the way to customize this for your own containers would be to define your own separate `erase_if` in your own namespace, which could forward on to `std::erase_if` or your own containers'. – Justin Jul 02 '19 at 20:33
  • 1
    @Justin No ADL means that an unqualified call, like `{ using std::erase_if; erase_if(...); }`, won't help to find ones own non-member `erase_if`, right? – Ted Lyngmo Jul 02 '19 at 20:45
  • 2
    @TedLyngmo You could write that, and it would find your own `erase_if` (provided it can be found by ADL, such as if it is defined in the same namespace as your container). – Justin Jul 02 '19 at 20:46
  • Whatever for do you force the lambdas to have return-type `bool`? – Deduplicator Jul 03 '19 at 16:01
  • @Deduplicator It is a matter of taste. I would say. being explicit `ret`in *[ captures ] ( params ) -> ret { body }* (about lambdas), not a bad practice, IMHO. – JeJo Jul 03 '19 at 16:05