Home > Net >  C , std::remove_if with std::string. Resize string by popping it's end in lambda
C , std::remove_if with std::string. Resize string by popping it's end in lambda

Time:11-23

Since std::remove() does not resize std::string, I've came up with an idea how to solve this:

std::string s{"aabcdef"};
std::remove_if(s.begin(), s.end(), [&s](char a)
    {
        if (a == 'a')
        {
            s.pop_back();
            return true;
        }
        return false;
    });

Excepted output:

bcdef

Actual output:

bcd

For some reason, it erases 4 characters instead of expected 2. If I try to pass an int instead and count true calls in lambda, it works as expected:

int removes{};
std::remove_if(s.begin(), s.end(), [&removes](char a)
    {
        if (a == 'a')
        {
                  removes;
                return true;
        }
        return false;
    });
for (int i{}; i < removes;   i) s.pop_back(); //s = "bcdef"

CodePudding user response:

std::remove() doesn't really remove anything, it only moves the matching elements to the end of the range, and then returns a past-the-end iterator for the new end of the range.

To finally remove the characters from the string, you can call string::erase() specifying that iterator and the current end iterator to shrink the string to the reduced size:

std::string s{ "aabcdef" };

auto it = std::remove(s.begin(), s.end(), 'a');
s.erase(it, s.end());

std::cout << s << '\n';

If is availabe, a very straightforward solution is to call std::erase():

std::string s{ "aabcdef" };
std::erase(s, 'a');

std::cout << s << '\n';

output:

bcdef

CodePudding user response:

Note the function/lambda you passed to std::remove_if isn't about what happened to the character you want to remove. It is about how to determine which character need to be removed. What that means is whatever happens within the lambda will happen before std::remove_if starting its removal algorithm.

In your case, since you called pop_back() within the lambda, you are actually removing the last letter before remove_if can identify which character need to be removed.


Another thing to note is, if std::remove doesn't actually remove, and your usage should only call pop_back() twice, how come you end up with 3 characters, instead of 5?

The reason is because when you call the function remove_if(begin_it, end_it, some_predicate), some_predicate will be invoked by every element between begin_it and end_it, regardless of what happens within some_predicate. Note that both iterators are determined before any calls of the predicate, so even when you did pop_back() within the predicate, remove_if will continue running the predicate after the last character.

In your case, since you have popped 2 characters, remove_if will actually run predicate on and shift both the null character and the character after the null character. In fact, if you run s.size(), you will notice the size is still 5 not 3.


To properly remove the characters, you should either use the erase-remove idiom:

s.erase(std::remove_if(s.begin(), s.end(), [](char c){ return c == 'a'; }));
// or
s.erase(std::remove(s.begin(), s.end(), 'a');

Or for C 20 or later, you can use:

std::erase_if(s, [](char c){ return c == 'a'; });
// or
std::erase(s, 'a');
  • Related