为什么 views::reverse 可以将 non-sized_range 转换为 size_range?

问题描述

[range.sized#1]中:

sized_­range 概念细化了范围,要求 范围内的元素数量可以在摊销中确定 恒定时间使用ranges​::​size

template<class T>   
  concept sized_­range =
  range<T> &&
    requires(T& t) { ranges::size(t); };

标准规定获得ranges::sized_range的大小保证在恒定时间内。考虑以下几点:

auto r1 = std::views::iota(0)
    | std::views::filter([](int x){ return x % 2 == 0; })
    | std::views::take(1'000'000);

r1 显然不是 sized_range,因为不可能在 常数时间,这也意味着我们使用 ranges::size 来评估其大小也是格式错误的。

但是我偶然发现如果我们在它上面应用views::reverse,新的范围r2突然变成了sized_range,我们可以直接使用ranges::size得到它的大小正确,godbolt

auto r2 = r1 | views::reverse;

static_assert(!ranges::sized_range<decltype(r1)>);
static_assert( ranges::sized_range<decltype(r2)>);
std::cout << std::ranges::size(r2) << "\n"; // print 500'000

然而,很明显,新范围 r2 不是 sized_range,因为我们永远无法在恒定时间内获得它的大小,这似乎违反了标准所说的。

为什么 views::reverse 可以将非sized_range 转换为 sized_range?显然,这种转换不会对原始范围的大小产生任何影响。这是标准缺陷还是库错误

解决方法

要求是摊销恒定的,并不总是恒定的。

  • take_view<...> 产生 counted_iterator
  • 所以 reverse_view<take_view<...>> 产生 reverse_iterator<counted_iterator<...>>
  • counted_iterator 始终可以减去:您只需减去计数即可。
  • 所以 reverse_iterator<counted_iterator<...>> 也总是可以减去。
  • ranges::size 是为迭代器/哨兵模型 sized_sentinel_for 的任何范围定义的。这包括reverse_view<take_view<...>>

为了满足分摊的常数复杂度要求,reverse_view::begin 会在需要计算源范围的末尾时缓存它(即源范围不常见)。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...