为什么 views::reverse 不适用于 iota_view<int64_t, int64_t>

Why does views::reverse not work with iota_view<int64_t, int64_t>

我有 following C++ program,但由于某些原因我不能使用 int64_t 作为模板参数。

#include <iostream>
#include <ranges>

template<typename T> 
void fn() {
    for (auto val : std::ranges::iota_view{T{1701}, T{8473}} 
                  | std::views::reverse
                  | std::views::take(5))
    {
        std::cout << val << std::endl;
    }

}

int main()
{
    fn<int16_t>();
    fn<int32_t>();
    // does not compile:
    // fn<int64_t>();
}

这是预期的(我做错了什么),还是 compiler/std 库中的一些不幸错误?

注意:当我删除 std::views::reverse 代码时,也会为 int64_t 编译。

这是一个 libstdc++ 错误,已提交 100639


iota 是一个非常复杂的范围。特别是,我们需要选择一个 difference_type ,它对于我们正在递增的类型来说足够宽,以避免溢出(另请参见 P1522). As a result, we have in [range.iota]:

Let IOTA-DIFF-T(W) be defined as follows:

  • [...]
  • Otherwise, IOTA-DIFF-T(W) is a signed integer type of width greater than the width of W if such a type exists.
  • Otherwise, IOTA-DIFF-T(W) is an unspecified signed-integer-like type ([iterator.concept.winc]) of width not less than the width of W.

[Note 1: It is unspecified whether this type satisfies weakly_­incrementable. — end note]

对于iota_view<int64_t, int64_t>,我们的差异类型是__int128(一个足够宽的有符号整数类型)。在 gcc 上,signed_integral<__int128> 在符合模式 (-std=c++20) 下编译时为 false,在扩展 (-std=gnu++20) 下为 true

现在,在 libstdc++ 中,reverse_viewimplemented as:

template<typename _Iterator>
class reverse_iterator
  : public iterator<typename iterator_traits<_Iterator>::iterator_category,
                    typename iterator_traits<_Iterator>::value_type,
                    typename iterator_traits<_Iterator>::difference_type,
                    typename iterator_traits<_Iterator>::pointer,
                    typename iterator_traits<_Iterator>::reference>
{
  // ...
  typedef typename __traits_type::reference reference;
  // ...
  _GLIBCXX17_CONSTEXPR reference operator*() const;
  // ...
};

这不是 reverse_iterator 的指定方式。 [reverse.iterator]reference 类型定义为:

using reference = iter_reference_t<Iterator>;

不同的是,后者只是表示*it的类型,而前者实际上是通过iterator_traits并试图确定reference是什么意思,如果It::reference不作为一种类型存在。该决定在 [iterator.traits]:

中指定

Otherwise, if I satisfies the exposition-only concept cpp17-input-iterator, iterator_­traits<I> has the following publicly accessible members: [...]

其中 reference 如果存在则为 I::reference,如果不存在则为 iter_reference_t<I>。那个貌似是一样的,不过要先满足cpp17-input-iterator<I>cpp17-input-iterator<I> 要求,除其他外:

template<class I>
concept cpp17-input-iterator =
  cpp17-iterator<I> && equality_­comparable<I> && requires(I i) {
    // ...
    requires signed_­integral<typename incrementable_traits<I>::difference_type>;
  };

所以基本上,当且仅当 signed_integral<__int128> 成立时,iterator_t<iota_view<int64_t, int64_t>> 满足 cpp17-input-iterator,这只有在我们在 -std=gnu++20.[=58= 中编译时才是正确的]

但我们不需要满足这个要求,因为 reverse_iterator<I> 应该直接使用 iter_reference_t<I> 而不是通过 iterator_traits,这需要检查 signed_integral<__int128>.