范围适配器对于可视范围背后的参数是否懒惰?

neo*_*nxc 2 c++ c++20 std-ranges

C++20 标准在[range.adaptors.general]中表示范围适配器

当迭代结果视图时延迟评估。

另一方面,在[range.filter.view]中,filter_view 的 begin 函数有一个注释,提到了缓存结果。那么适配器的懒惰程度如何呢?

当执行以下代码时:

#include <iostream>
#include <ranges>

void print(std::ranges::range auto&& r)
{
    for (const auto& item : r)
    {
        std::cout << item << ", ";
    }
    std::cout << " <end of range>\n";
}

int main()
{
    using namespace std::ranges;

    bool filter = false;
    
    auto v = iota_view{4, 10} | views::filter([&filter](auto item){return filter;});

    // multipass guarantee
    static_assert(std::ranges::forward_range<decltype(v)>);

    filter = true;
    print(v);

    filter = false;
    print(v);

    filter = true;
    print(v);
}
Run Code Online (Sandbox Code Playgroud)

是否保证适配器会尊重filter变量的值?如果不是,我正在调用什么样的行为以及在哪里声明?

Nic*_*las 7

请记住,在 C++ 迭代器模型中,定位和访问是两个不同的操作。然而,过滤迭代器是一种其位置基于访问其正在过滤的范围的迭代器。这就是迭代器的本质。

要查找过滤范围的开头,需要找到基础范围中与过滤条件匹配的第一个位置。就像在过滤范围中查找下一个元素需要迭代一样,直到到达与过滤条件匹配的另一个迭代器。

因此,获取过滤范围的起始迭代器需要访问该范围的至少一个元素。过滤迭代器在完成其工作的同时尽可能地惰性。

但是,您的特定代码表现出 UB,因为您的谓词不是regular_invocable. 该标准明确要求:

invoke 函数调用表达式应保持相等性 ( [concepts.equality] )

这意味着:

如果给定相同的输入,则表达式会产生相同的输出,则该表达式是保持相等的。表达式的输入是表达式操作数的集合。表达式的输出是表达式的结果和表达式修改的所有操作数。

您通过更改谓词的行为违反了该要求。