为什么`iota(0)take(0)`不是C++20中的模型范围::sized_range?

考虑以下代码片段:

#include <ranges>

auto r = std::views::iota(0) | std::views::take(0);
static_assert(std::ranges::sized_range<decltype(r)>);

gcc-trunk拒绝它,因为required-expression std::ranges::size(r)无效。为什么不r建模ranges::sized_range,即,为什么我不能std::ranges::size在它上面使用?

更新

使用range-v3后编译。C++23 需要此功能,还是 LWG 问题?

#include <range/v3/all.hpp>
#include <ranges>

auto r = ranges::views::iota(0) | ranges::views::take(0);
static_assert(std::ranges::sized_range<decltype(r)>);

这个问题似乎是在sentinel_trrange-v3只是ranges::default_sentinel其中满足std::sized_sentinel_for<ranges::counted_iterator>,因为有一个有效operator-的[predef.iterators#iterators.counted]对于这两种类型:

friend constexpr iter_difference_t<I> operator-(
  const counted_iterator& x, default_sentinel_t);
friend constexpr iter_difference_t<I> operator-(
  default_sentinel_t, const counted_iterator& y);

但是在 中namepace std::rangessentinel_tof risstd::ranges::take_view<std::ranges::iota_view>::_Sentinel<true>不能转换为std::default_sentinel_t.

回答

views::take如果给定的范围本身已调整大小,则仅产生一个调整大小的范围。并且views::iota 不是大小范围,除非您使用赋予它大小的二元素构造函数之一。你没有。

至于为什么take_view只在底层迭代器调整大小时才调整大小,这是因为take_view当达到要获取的元素数量到达底层范围的末尾时停止。这意味着尺寸可能小于您要求的尺寸。因此,要计算 a 的大小take_view,您必须能够计算基础范围的大小,以查看它是否小于给定的计数。如果您碰巧传递了一个永远不需要计算大小的计数,这并不重要;它是一个编译时属性,而不是基于您在运行时碰巧赋予它的值。

Range V3 如何“工作”尚不得而知,但 C++20 标准不允许它工作。

  • @康桓瑋 That is *possible*, but not what the committee chose to specify

回答

使用 range-v3 后编译。C++23 需要此功能,还是 LWG 问题?

没有也没有。

take(0)是一个奇怪的例子,因为它表明0可能很重要——如果我们可以根据价值做出决定,那么take(0)总是会给你empty<T>正确的T(这是一个sized_range)。

所以让我们考虑take(5)。

take(5)为您提供最多 5包含元素的范围。但是只有知道输入范围内有多少个元素,我们才能知道有多少,并且只有当输入范围是 a 时sized_range,我们才能知道,这就是 C++20 的take操作方式。但实际上还有另一种方法可以知道有多少元素r | take(5)r不是 a sized_range:如果我们知道这r是一个无限范围。显然,5从无限范围中取出元素会给你一个范围的 with5元素(最后我检查过,无穷实际上大于5,即使对于非常大的 值5)。

在 range-v3 中,iota(0)是无限范围。我的意思是,它在 C++20 中也是无限范围,但 C++20 范围没有这种无限范围的概念,而range-v3 有:

template<typename From, typename To /* = unreachable_sentinel_t*/>
struct RANGES_EMPTY_BASES iota_view
  : view_facade<iota_view<From, To>,
                same_as<To, unreachable_sentinel_t>
                    ? infinite
                    : std::is_integral<From>::value && std::is_integral<To>::value
                          ? finite
                          : unknown>
{

我们这里的情况满足same_as<To, unreachable_sentinel_t>,所以我们传入infiniterange-v3 所指的“基数”。

take然后检测到它的输入是无限的并default_sentinel作为哨兵返回:

CPP_auto_member
constexpr auto CPP_fun(end)()(const //
    requires range<Rng const>)
{
    if constexpr(sized_range<Rng const>)
        if constexpr(random_access_range<Rng const>)
            return ranges::begin(base_) +
                   static_cast<range_difference_t<Rng>>(size());
        else
            return default_sentinel;
    // Not to spec: Infinite ranges:
    else if constexpr(is_infinite<Rng const>::value)
        return default_sentinel;
    else
        return sentinel<true>{ranges::end(base_)};
}

所以在这种情况下,我们的iterator/sentinel对是counted_iterator/ default_sentinel,这对满足sized_sentinel_for,所以ranges::size有效。当计数达到零并且减法简单地否定计数时,它们是相等的。

C++20 在设计上没有这种无限范围的概念。目前还不清楚将来是否会这样做。我什至不确定 Eric Niebler 和 Casey Carter 对 range-v3 中的设计是否满意。


以上是为什么`iota(0)take(0)`不是C++20中的模型范围::sized_range?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>