考虑使用范围库的以下代码(来自 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
问题是,为什么匹配过滤器谓词的值被打印两次?
我在 CppCon 2020 的演示文稿中看到了这段代码,演示者解释了为什么会发生这种情况。根据他的说法,过滤器会迭代,直到满足其谓词(当然,如果每次都需要调用transform
)。然后
filter
停止并准备好进行迭代。之后,实际迭代开始,并从
filter
读取一个值,然后针对相同的输入再次调用
transform
。我不清楚为什么这是必要的。由于
ranges::views
会延迟计算值,并且每个视图操作都会从之前的视图操作中提取数据,为什么过滤器不能在找到匹配项后立即将值传递给管道中位于其之后的值?
为什么不能在找到匹配项后立即将值传递给管道中位于该值之后的任何人?因为在迭代器模型中,定位和访问是不同的操作。您可以使用
++
定位迭代器;您可以使用
*
访问迭代器。这是两个不同的表达式,在两个不同的时间进行评估,从而产生两个不同的函数调用,从而产生两个不同的值(
++
产生迭代器,
*
产生引用)。过滤迭代器为了执行其迭代操作,必须访问其底层迭代器的值。但是该访问无法传达给
++
的调用者,因为调用者只要求定位迭代器,而不是获取其值。定位迭代器的结果是一个新的迭代器值,而不是存储在该迭代位置的值。所以没有人可以归还。
您不能真正延迟定位直到访问之后,因为用户可能会多次重新定位迭代器。我的意思是,理论上你可以通过存储此类增量/减量的数量来实现它。但这增加了迭代器实现的复杂性。特别是因为解决这种延迟定位可以通过像针对另一个迭代器或哨兵进行测试这样简单的事情来实现,这应该是一个 O(1) 操作。
这只是迭代器模型的限制,因为它同时具有位置和值。迭代器模型被设计为指针的抽象,其中迭代和访问是不同的操作,因此它继承了这种机制。存在将迭代和访问捆绑在一起的替代模型,但它们不是标准库迭代的工作方式。