考虑使用范围库的以下代码(来自 c++20)
#include <iostream>
#include <ranges>
#include <vector>
int main() {
std::vector<int> inputs{1, 2, 3, 4, 5, 6};
auto square_it = [](auto i) {
std::cout << i << std::endl;
return i * 2; };
auto results = inputs | std::views::transform(square_it) | std::views::filter([](auto i){ return i % 3 == 0; });
for(auto r : results) {
// std::cout << r << std::endl;
}
}
cout
在 square
功能是登录时square
函数由范围库调用。此代码打印1
2
3
3
4
5
6
6
问题是,为什么与过滤器谓词匹配的值会打印两次? 我在 presentation in CppCon 2020 中看到了这段代码,其中演示者解释了为什么会发生这种情况。据他说,过滤器迭代直到它的谓词得到满足(当然如果每次都需要调用
transform
)。然后filter
停止并准备好进行迭代。之后,实际的迭代开始并从 filter
中读取一个值。 ,然后调用 transform
再次为相同的输入第二次。我不清楚为什么这是必要的。自
ranges::views
懒惰地计算值,并且每个 View 操作都从它之前的操作中提取数据,为什么不能过滤后在找到匹配项后立即将值传递给管道中的任何人?
最佳答案
why can't filter just pass the value to whoever is after it in the pipeline as soon as it finds a match?
因为在迭代器模型中,定位和访问是不同的操作。你用
++
定位一个迭代器;您可以使用 *
访问迭代器.这是两个不同的表达式,它们在两个不同的时间进行评估,导致两个不同的函数调用产生两个不同的值(++
产生一个迭代器,*
产生一个引用)。过滤迭代器为了执行它的迭代操作,必须访问它的底层迭代器的值。但是该访问权限无法传达给
++
的调用者因为那个调用者只要求定位迭代器,而不是获取它的值。定位迭代器的结果是一个新的迭代器值,而不是存储在该迭代位置的值。所以没有人可以归还。
您不能真正延迟定位直到访问之后,因为用户可能会多次重新定位迭代器。我的意思是,理论上你可以通过存储这种增量/减量的数量来实现它。但这增加了迭代器实现的复杂性。特别是因为解决这种延迟定位可以通过像测试另一个迭代器或哨兵这样简单的事情发生,这应该是 O(1) 操作。
这只是迭代器模型的一个限制,因为它同时具有位置和值。迭代器模型被设计为指针的抽象,其中迭代和访问是不同的操作,因此它继承了这种机制。存在迭代和访问捆绑在一起的替代模型,但它们不是标准库迭代的工作方式。
关于c++ - 为什么 C++ 范围 "transform -> filter"为匹配过滤器谓词的值调用转换两次?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64199664/