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.
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 ofWif 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 ofW.[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
Isatisfies the exposition-only conceptcpp17-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>.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With