为什么 C++ 范围“transform -> filter”为匹配过滤器谓词的值调用两次转换?

dar*_*amo 11 c++ c++20

考虑使用范围库的以下代码(来自 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

函数中的coutinsquare是记录square范围库何时调用该函数。此代码打印

1
2
3
3
4
5
6
6
Run Code Online (Sandbox Code Playgroud)

问题是,为什么匹配过滤器谓词的值会打印两次?

在 CppCon 2020演示文稿中看到了这段代码,演示者解释了为什么会发生这种情况。据他说,过滤器迭代直到它的谓词得到满足(当然如果transform每次都需要调用)。然后filter停止并准备好进行迭代。之后,实际的迭代开始并从 中读取一个值filter,然后transform再次调用相同的输入。

我不清楚为什么这是必要的。由于ranges::views懒惰地计算值并且每个视图操作都从它前面的操作中提取数据,为什么不能过滤器在找到匹配项后立即将值传递给管道中的任何人?

Nic*_*las 9

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

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

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

所以没有人可以归还。

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

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

  • 对于输入迭代器(即单遍范围),替代模型是否有任何缺点,如果没有,c ++ 20 中的 Ranges 功能不可能包装迭代器模型以适应替代模型,因此出现意外/避免低效行为? (2认同)