首页 > 解决方案 > 为什么 C++ 范围“变换 - > 过滤器”为匹配过滤器谓词的值调用变换两次?

问题描述

考虑使用范围库的以下代码(来自 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计算值是惰性的,并且每个视图操作都从它之前的数据中提取数据,为什么过滤器不能在找到匹配项后立即将值传递给管道中的任何人?

标签: c++c++20

解决方案


为什么过滤器不能在找到匹配项后立即将值传递给管道中的任何人?

因为在迭代器模型中,定位和访问是不同的操作。++您使用;定位迭代器 您使用 . 访问迭代器*。这是两个不同的表达式,它们在两个不同的时间进行评估,导致两个不同的函数调用产生两个不同的值(++产生一个迭代器,*产生一个引用)。

过滤迭代器为了执行其迭代操作,必须访问其底层迭代器的值。但是这种访问不能传达给调用者,++因为调用者只要求定位迭代器,而不是获取它的值。定位迭代器的结果是一个新的迭代器值,而不是存储在该迭代位置的值。

所以没有人可以退货。

您不能真正延迟定位直到访问之后,因为用户可能会多次重新定位迭代器。我的意思是,您可以通过存储此类增量/减量的数量在理论上以这种方式实现它。但这增加了迭代器实现的复杂性。特别是因为解决这种延迟定位可以通过像测试另一个迭代器或哨兵这样简单的事情来解决,这应该是一个 O(1) 操作。

这只是迭代器模型的限制,因为它同时具有位置和值。迭代器模型被设计为指针的抽象,其中迭代和访问是不同的操作,因此它继承了这种机制。存在将迭代和访问捆绑在一起的替代模型,但它们不是标准库迭代的工作方式。


推荐阅读