带可变谓词的std::erase_if在新旧编译器中行为差异的原因咨询
带可变谓词的std::erase_if在新旧编译器中行为差异的原因咨询
嘿,我最近碰到一个挺有意思的问题——用带可变状态谓词的std::erase_if时,不同版本的编译器跑出来的结果居然不一样,特意来跟大家聊聊这背后的原因。
先给大家看我测试的代码:
#include <algorithm> #include <iostream> #include <set> #include <vector> int main () { std::vector<int> v(5, 0); for (auto i : v) std::cout << ' ' << i; std::cout << '\n'; std::erase_if(v, [seen = std::set<int>{}] (int i) mutable { return !seen.insert(i).second; }); for (auto i : v) std::cout << ' ' << i; std::cout << '\n'; return 0; }
这段代码的逻辑很简单:用std::erase_if配合一个带mutable关键字的lambda,lambda里用std::set记录已经出现过的元素,意图是移除容器里的重复元素,同时保留原有顺序。
但不同编译器版本跑出来的结果差得有点多:
- 当使用GCC版本低于12、Clang版本低于15时,输出是:
0 0 0 0 0
0 0 - 当使用GCC版本12.1及以上、Clang版本15及以上时,输出是:
0 0 0 0 0
0
后来我搞清楚了,这其实是因为C++标准对std::erase_if的调用规则做了澄清,新版本的编译器跟进了这个标准变化。
在旧版本编译器的实现里,std::erase_if可能会对容器里的同一个元素多次调用传入的谓词。对于我们这种带可变状态的谓词(比如这里lambda里的seen集合,因为mutable修饰,内部状态可以被修改),多次调用会提前改变谓词的内部状态,导致后续元素的判断逻辑出错——就像这个例子里,旧编译器可能在处理过程中多次调用了lambda,让seen集合的状态提前变化,最后误保留了两个重复的0。
而新版本的编译器则严格遵循了标准的最新要求,确保容器里的每个元素只会被谓词评估一次。这样一来,可变状态的谓词只会按元素遍历的顺序正常更新,最终正确只保留了一个唯一的0。
内容来源于stack exchange
相关产品推荐
相关产品推荐

