Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I have the following C++ program, and for some reason I can not use int64_t as template argument.

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

Is this expected(I am doing something wrong), or is it just some unfortunate bug in compiler/std lib?

Note: when I remove std::views::reverse code compiles for int64_t also.

like image 688
NoSenseEtAl Avatar asked May 17 '21 16:05

NoSenseEtAl


1 Answers

This is a libstdc++ bug, submitted 100639.


iota is a surprisingly complex range. In particular, we need to pick a difference_type that is sufficiently wide for the type that we're incrementing to avoid overflow (see also 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]

For iota_view<int64_t, int64_t>, our difference type is __int128 (a wide-enough signed integral type). On gcc, signed_integral<__int128> is false when compiling in conforming mode (-std=c++20) and true with extensions (-std=gnu++20).

Now, in libstdc++, reverse_view is implemented 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;
  // ...
};

This isn't how reverse_iterator is specified. [reverse.iterator] defines the reference type as:

using reference = iter_reference_t<Iterator>;

The difference is that the latter just means the type of *it, while the former actually goes through iterator_traits and tries to determine what reference means if It::reference doesn't exist as a type. That determination is specified in [iterator.traits]:

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

where reference is I::reference if it exists or iter_reference_t<I> if it doesn't. That looks like it's the same thing, but we have to first satisfy cpp17-input-iterator<I>. And cpp17-input-iterator<I> requires, among other things:

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

So basically, iterator_t<iota_view<int64_t, int64_t>> satsifies cpp17-input-iterator if and only if signed_integral<__int128> holds, which is only true if we're compiling in -std=gnu++20.

But we shouldn't need to meet this requirement, since reverse_iterator<I> should just directly use iter_reference_t<I> and not go through iterator_traits, which side-steps having to check signed_integral<__int128>.

like image 176
Barry Avatar answered Sep 19 '22 13:09

Barry