std::erase_if能够删除std::vector中的多余元素吗?

13

我使用std::erase_if和一个计数器来删除容器中的一半元素,代码如下。使用gcc10编译C++20。

#include <iostream>
#include <vector>
#include <map>
#include <unordered_map>

int main()
{
    {
        std::vector<int> container(10);
        std::cout << container.size() << std::endl;
        std::erase_if(container, [i = 0u](auto&&...) mutable { return i++ % 2 == 0; });
        std::cout << container.size() << std::endl;
    }
    std::cout << std::endl;
    {
        std::map<int, int> container;
        for (int i = 0; i < 10; i++) {
            container.emplace(i, i);
        }
        std::cout << container.size() << std::endl;
        std::erase_if(container, [i = 0u](auto&&...) mutable { return i++ % 2 == 0; });
        std::cout << container.size() << std::endl;
    }
    std::cout << std::endl;
    {
        std::unordered_map<int, int> container;
        for (int i = 0; i < 10; i++) {
            container.emplace(i, i);
        }
        std::cout << container.size() << std::endl;
        std::erase_if(container, [i = 0u](auto&&...) mutable { return i++ % 2 == 0; });
        std::cout << container.size() << std::endl;
    }
}
输出结果出乎意料。对于向量,多了一个元素被删除:
10
4

10
5

10
5

我打印出结果发现 vector[1] 是被意外移除的元素

尽管这通常不是erase_if的正常用法,但我仍然很好奇为什么它只发生在vector而不是其他的map上。我猜这可能与迭代器类型有关。如果有人能给出详细的解释,我将不胜感激。


1
“iterator type shenanigans” 是什么意思? - molbdnilo
3
我认为这是 g++ 的一个 bug。它在主干版本中可以正常工作,workingbuggy,而且在 clang++ 中也可以正常工作。 - Ted Lyngmo
1
似乎只有在使用复制捕获(就像在这种情况下)时才会发生这种情况。如果您通过引用捕获a,或者将static unsigned i = 0;添加到lambda中,则可以正常工作。很奇怪,当在不同的算法中使用时,它的行为是不同的。我猜测gcc复制了lambda并在std::erase_if(vector)中使用了一次。@AdrianMole 是的,我在VS2022中也遇到了相同的问题。 - Ted Lyngmo
1
将函数对象作为参数的算法可以自由复制这些函数对象。请参阅标准中的此注释 - molbdnilo
3
可变 lambda 表达式不满足 regular_invocable 概念,这意味着相同的输入可能会产生不同的结果。我严重怀疑在将其用作谓词时会导致未定义行为。 - 康桓瑋
显示剩余2条评论
1个回答

13

remove_if需要一个谓词(Predicate)。标准库要求谓词类型

给定一个类型为(可能是const)T的glvalue u,它指代与*first相同的对象,则pred(u)必须是一个有效的表达式,等于pred(*first)

您的谓词会更改其内部状态。因此,使用相同的元素调用两次将产生不同的结果。这意味着它不符合谓词的要求。

因此,会出现未定义行为。


哎呀,这真是一颗难以咽下的苦药。这意味着即使通过引用捕获i,对于同一元素可能会产生不同的结果 - 因此,即使在这种情况下Predicate不改变其内部状态,它也会导致UB? - Ted Lyngmo
按引用捕获并不意味着可变。 - Kobi
@Kobi 不,它并没有,这正是我的观点。它有 UB 并不是因为它是可变的,改变了它的内部状态,而是因为它不一定会为相同的元素(如果多次提供给它)提供相同的结果,这使得它具有 UB。 - Ted Lyngmo
@TedLyngmo 我理解你的观点。重新阅读并刷新我的记忆,也许问题出在你复制 lambda 的位置上。使用 ref,则不会有影响。它是不可变的 lambda,而 ref 被复制,尽管它指向/引用了该对象。当复制可变的 lambda 时,成员现在是不同的。这可能是违规行为。有人评论说,使用 ref 就不会发生这种情况。但无论如何,我认为问题在于可变性未满足某些先决条件。 - Kobi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接