范围适配器在可视范围背后的参数方面是否懒惰?



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

是否可以保证适配器会尊重变量filter值?如果不是,我援引的是什么样的行为,在哪里陈述?

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

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

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

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

调用

函数调用表达式应保持相等([概念.平等])

这意味着:

如果给定相等的输入,则表达式的结果相等,则该表达式是相等的。表达式的输入是表达式的操作数集。表达式的输出是表达式的结果以及表达式修改的所有操作数。

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

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

不。

如果不是,我援引的是什么样的行为,在哪里陈述?

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

如果声明约束的语义需求([structure.requirements])未在使用点建模,则程序格式不正确,无需诊断。

特别是,filter_view要求谓词评估是保持相等的(通过indirect_­unary_­predicate这需要predicate这需要regular_invocable)。其结果可以自发改变的东西不符合这个要求 - 参见[概念.平等]/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仅对打印所需的项目调用,无论视图顺序如何。这显示了懒惰。

最新更新