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 ofW
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 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
I
satisfies 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