Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does C++ standard library provide more compact and generalized version of the erase–remove idiom?

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 c++17, or will there be such a utility in c++20?

like image 829
JeJo Avatar asked Jul 02 '19 20:07

JeJo


1 Answers

Not in the scope of c++17, but c++20 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

  • For sequence containers like std::vector, std::deque and for std::std::basic_string, it will be equivalent to

    container.erase(
           std::remove_if(container.begin(), container.end(), unaryPredicate)
           , container.end()
    );
    
  • For sequence containers std::forward_list and std::list, it will be equivalent to

    container.remove_if(unaryPredicate);
    
  • For ordered associative containers(i.e. std::set, std::map, std::multiset and std::multimap) and unordered associative containers(i.e. std::unordered_set, std::unordered_map, std::unordered_multiset and std::unordered_multimap), the std::erase_if is equivalent to

    for (auto i = container.begin(), last = container.end(); i != last; ) 
    {
      if (unaryPredicate(*i)) 
      {
        i = container.erase(i);
      }
      else
      {
        ++i;
      }
    }
    

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

std::erase(container, value_to_be_removed);
like image 54
JeJo Avatar answered Nov 10 '22 07:11

JeJo