为什么在使用`std::views::reverse`时对过滤函数的调用是多余的?

我试图了解通过管道运算符(视图/适配器)传递时对过滤器函数的调用顺序。我看到的结果根本不直观。虽然可能有原因,但如果有人能解决这个问题,我将不胜感激。也可以指向cppreference.com上的正确文档。

#include <vector>
#include <ranges>
#include <iostream>

int main() {
    const auto vec = std::vector{1,2,3,4,5,6};
    auto filter = [](const auto f) {
        std::cout << "f = " << f << ", "; 
        return f % 2 == 0;
    };

    std::cout << std::endl;
    for (auto v : vec
        | std::views::reverse
        | std::views::filter(filter)
        | std::views::take(2)
        | std::views::reverse)
    {
        std::cout << std::endl << "v = [" << v << "]" << std::endl;
    }
}

实际结果:

f = 6, f = 5, f = 4, f = 3, f = 2, f = 3, f = 4, 
v = [4]
f = 3, f = 4, f = 5, f = 6, 
v = [6]
f = 5, f = 6, 

预期结果:

f = 6, f = 5, f = 4, f = 3, f = 2, f = 1, 
v = [4]
v = [6]

这是上述代码的Godbolt示例。这里还有一些代码,我试着分解它来理解。但没有什么是显而易见的。

回答

所讨论的基于范围的 for 循环可以重写为

auto&& range = vec
    | std::views::reverse
    | std::views::filter(filter)
    | std::views::take(2)
    | std::views::reverse;

auto begin = range.begin();
auto end = range.end();

for (; begin != end; ++begin) {
    auto v = *begin;
    std::cout << std::endl << "v = [" << v << "]" << std::endl;
}
  1. 的初始化程序range只构建视图。什么都不输出。
  2. range.begin()返回reverse_iteratorbase()是一个迭代底层视图的结束。为了找到底层视图的结尾,进行了 5 次调用filter(对应于f = 6, f = 5, f = 4, f = 3, f = 2, )。
  3. range.end()返回 a ,reverse_iteratorbase()是基础视图开头的迭代器。的开头filter_view已被缓存。没有调用到filter
  4. begin != end 返回真。
  5. *begin递减底层基迭代器的副本,以便访问反向范围的第一个元素。这导致接下来的 2 次调用filter(对应于f = 3, f = 4, )。
  6. 输出第一个元素的值。( v = [4])
  7. ++begin递减基础基迭代器。( f = 3, f = 4, )
  8. begin != end 返回真。
  9. *begin递减底层基迭代器的副本以访问第二个元素。( f = 5, f = 6, )
  10. 输出第二个元素的值。( v = [6])
  11. ++begin递减基础基迭代器。( f = 5, f = 6, )
  12. begin != end 返回假。

以上是为什么在使用`std::views::reverse`时对过滤函数的调用是多余的?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>