【问题标题】:Are range adaptors lazy in regard to arguments behind the viewable range?范围适配器对于可视范围后面的参数是否懒惰?
【发布时间】:2021-03-23 17:13:45
【问题描述】:

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

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

另一方面,remark in [range.filter.view] for filter_view's begin function 提到缓存结果。那么适配器的懒惰到什么程度呢?

执行以下代码时:

#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);
}

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

【问题讨论】:

    标签: c++ c++20 std-ranges


    【解决方案1】:

    请记住,在 C++ 迭代器模型中,positioning and access 是两个不同的操作。但是,过滤迭代器的位置基于访问它正在过滤的范围。这就是迭代器的本质。

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

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

    但是,您的特定代码显示 UB,因为您的谓词 is not a regular_invocable。该标准明确要求:

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

    这意味着:

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

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

    【讨论】:

      【解决方案2】:

      是否保证适配器会尊重过滤器变量的值?

      没有。

      如果不是,我在调用什么样的行为?它在哪里声明?

      这是[res.on.requirements]/3的IFNDR:

      如果声明约束的语义要求 ([structure.requirements]) 没有在使用时建模,则程序格式错误,不需要诊断。

      特别是,filter_view 要求谓词评估保持相等(通过 indirect_­unary_­predicate 需要 predicate 需要 regular_invocable)。结果可以自发改变的东西不符合此要求 - 请参阅[concepts.equality]/3

      【讨论】:

        【解决方案3】:

        演示是最好的:https://godbolt.org/z/WxrsfTrve

        int squreIt(int x)
        {
            std::cout << "squreIt(" << x << ")\n";
            return x * x;
        }
        
        int main()
        {
            std::array a{4, 3, 2, 1, 0, 5, 6};
        
            for (auto x : a | std::views::transform(squreIt) | std::views::drop(3)) {
                std::cout << "Result = " << x << '\n';
            }
            std::cout << "---------\n";
            for (auto x : a | std::views::drop(3) | std::views::transform(squreIt)) {
                std::cout << "Result = " << x << '\n';
            }
            return 0;
        }
        

        注意squreIt 仅对打印所需的项目调用,无论视图顺序如何。这表明了懒惰。

        【讨论】:

        • 很抱歉不同意。演示可能会也可能不会反映标准保证,尤其是在新功能的情况下。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2022-01-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-04-15
        • 1970-01-01
        相关资源
        最近更新 更多