【问题标题】:Why can views::reverse transform a non-sized_range into a size_range?为什么 views::reverse 可以将 non-sized_range 转换为 size_range?
【发布时间】:2021-07-15 13:55:22
【问题描述】:

[range.sized#1]:

sized_­range 概念根据以下要求细化范围 范围内的元素数量可以在 amortized 中确定 恒定时间使用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?显然,这种转换不会对原始范围的大小产生任何影响。这是标准缺陷还是库错误?

【问题讨论】:

  • 请注意,r2.size() 确实以 candidate: 'constexpr auto std::ranges::reverse_view&lt;_Vp&gt;::size() requires sized_range&lt;_Vp&gt; 失败。
  • “amortized”在这里做了很多工作。

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


【解决方案1】:

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

  • take_view&lt;...&gt; 产生 counted_iterators。
  • 所以reverse_view&lt;take_view&lt;...&gt;&gt; 产生reverse_iterator&lt;counted_iterator&lt;...&gt;&gt;
  • counted_iterators 总是可以减去:你只需减去计数。
  • 所以reverse_iterator&lt;counted_iterator&lt;...&gt;&gt; 也总是可以减去。
  • ranges::size 是为其迭代器/哨兵模型 sized_sentinel_for 的任何范围定义的。其中包括reverse_view&lt;take_view&lt;...&gt;&gt;

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

【讨论】:

    猜你喜欢
    • 2021-08-06
    • 2014-10-06
    • 2014-05-31
    • 1970-01-01
    • 2012-12-30
    • 2011-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多