Почему views::reverse не работает с iota_view‹int64_t, int64_t›

У меня есть следующая программа на C++, и по какой-то причине я не могу использовать 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>();
}

Ожидается ли это (я делаю что-то не так), или это просто какая-то досадная ошибка в компиляторе/std lib?

Примечание: когда я удаляю std::views::reverse, код также компилируется для int64_t.


person NoSenseEtAl    schedule 17.05.2021    source источник
comment
Вы можете уменьшить проблему до godbolt.org/z/vzxWjzYqr, и вы получите суп из ошибок шаблона. Любопытный!   -  person bradgonesurfing    schedule 17.05.2021
comment
godbolt.org/z/3PM7Wzz4q работает с C++2b   -  person Gaurav Sehgal    schedule 17.05.2021
comment
Вы не включили определения int16_t, int32_t и int64_t.   -  person eerorika    schedule 17.05.2021
comment
Отвечает ли это на ваш вопрос? C++20 слишком много диапазонов | операторы?   -  person Gaurav Sehgal    schedule 17.05.2021
comment
@GauravSehgal Думаю, нет, поскольку, хотя я не понимаю этот ответ, я не чувствую, что он объясняет разницу между int16 && int32 и int64.   -  person NoSenseEtAl    schedule 17.05.2021
comment
Кажется, что iota_view ненадежно работает в разных компиляторах. Этот godbolt.org/z/1PoGTMEsY работает с gcc, но не с clang, и это пример int32_t.   -  person bradgonesurfing    schedule 17.05.2021
comment
Clang даже не может скомпилировать приведенный на cppreference пример использования iota_view godbolt.org/z/1PjP6enqc. en.cppreference.com/w/cpp/ranges/iota_view   -  person bradgonesurfing    schedule 17.05.2021


Ответы (1)


Это ошибка libstdc++, отправлено 100639.


iota — удивительно сложный диапазон. В частности, нам нужно выбрать difference_type, достаточно широкий для типа, который мы увеличиваем, чтобы избежать переполнения (см. также P1522 ). В результате имеем в [range.iota]:

Пусть IOTA-DIFF-T(W) определяется следующим образом:

  • [...]
  • В противном случае IOTA-DIFF-T(W) является целочисленным типом со знаком, ширина которого больше, чем ширина W, если такой тип существует.
  • В противном случае IOTA-DIFF-T(W) является неуказанным целым числом со знаком ([iterator.concept.winc]) шириной не менее ширины W.

[Примечание 1: не указано, удовлетворяет ли этот тип weakly_­incrementable. — примечание в конце]

Для iota_view<int64_t, int64_t> нашим разностным типом является __int128 (достаточно широкий знаковый целочисленный тип). В gcc signed_integral<__int128> равно false при компиляции в соответствующем режиме (-std=c++20) и true с расширениями (-std=gnu++20).

Теперь в libstdc++ reverse_view равно реализовано как:

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]:

В противном случае, если I удовлетворяет концепции только экспозиции cpp17-input-iterator, iterator_­traits<I> имеет следующие общедоступные члены: [...]

где 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>;
  };

Таким образом, в основном, iterator_t<iota_view<int64_t, int64_t>> удовлетворяет cpp17-input-iterator тогда и только тогда, когда выполняется signed_integral<__int128>, что верно, только если мы компилируем в -std=gnu++20.

Но нам не нужно выполнять это требование, поскольку reverse_iterator<I> должен просто напрямую использовать iter_reference_t<I>, а не проходить через iterator_traits, что устраняет необходимость проверки signed_integral<__int128>.

person Barry    schedule 17.05.2021