拿着它
#include <range/v3/view/remove_if.hpp>
#include <range/v3/range/conversion.hpp>
#include <vector>
std::vector<int> foo(std::vector<int> v, bool(*p)(int)) {
return v | ranges::views::remove_if(p) | ranges::to_vector;
}
与此相比
#include <range/v3/action/remove_if.hpp>
#include <vector>
std::vector<int> bar(std::vector<int> v, bool(*p)(int)) {
return std::move(v) | ranges::actions::remove_if(p);
}
没有模板,只有两个 TU,每个 TU 都提供具有相同签名的纯函数。考虑到它们的实现,从调用者的角度来看,我期望这两个函数完成相同的任务。而他们似乎确实做到了这一点。
然而,它们编译后的代码完全不同,以至于 GCC(至少是主干)为后者生成更短的代码,而 Clang(主干)为前者生成更短的代码。
除了“编译器很难为这两个函数生成相同的代码”之外,我看不出这两个函数编译成不同代码的任何理由,但是什么让它变得如此困难?或者,如果我错了,为什么这两个函数必须编译成不同的汇编?
并且,除了基准测试之外,还有其他理由说明我为什么应该选择其中一种实现方式而不是另一种实现方式吗?
完整例子。
我甚至不确定这两种方法在理论上是否能生成相同的代码。让我们来看看这两种方法。
操作
对于操作,这是采取,就地
v
改变它以删除满足 的元素,然后返回相同的元素。这相当于编写:p
v
或者,在 C++20 之前:
肯定没有发生任何分配,我们只是移动了一堆
int
s 然后进行了更改v.size()
。视图
views::remove_if
v
是一个惰性过滤器。它为我们提供了不满足的元素的视图p
。然后,to_vector
将构造一个新的,这需要分配,并将不满足的vector
所有元素复制到新的中。返回该新向量。v
p
vector
它会
混合优化吗?最初,表达式
v | remove_if(p) | to_vector
分配一个vector<int>
不同于 的新值v
。v
在该表达式的整个长度内都处于活动状态,因此您无法v
在此处重用 的内存。这里的优化不仅仅是识别
v
即将被销毁的,以便可以重用其分配。而且新的vector
最多与相同的大小,v
因此重用其分配是一种可行的策略。而且这个新的元素vector
以允许重用该分配的方式填充。从根本上讲,这两种情况只是不同的算法。有时编译器可以解决这个问题,但这似乎太牵强了。如果存在这样的优化,它基本上是针对这种情况手工设计的。
一般来说,这个问题的答案是使用最具体的工具来完成这项工作。如果你有一个
vector<int>
,你只想要不满足的元素p
,而你根本不需要原始元素 - 那就是actions::remove_if
(或者,根据上下文,只是直接调用)。这就是为解决这一问题而构建的std::erase_if
工作。actions::remove_if
如果您不需要一个包含所有满足条件的元素的容器
p
,而只需要按需选择其中(一些)元素 - 那就是views::remove_if
。有时积极进取更好。有时懒惰更好。这确实取决于具体问题。