This code compiles and works correctly:
#include <ranges>
#include <iostream>
int main() {
const auto r = std::views::iota('a', static_cast<char>('g' + 1));
for(const auto& [start, end] : r | std::views::chunk(3u)) {
for(auto it = start; it != end; ++it) {
std::cout << *it << " ";
}
std::cout << "\n";
}
return 0;
}
Its output is:
a b c
d e f
g
If I change the definition of r as follows:
const auto r = std::views::iota('a', 'g' + 1);
The code does not compile. GCC 13 emits the following error:
chunk.cpp:13:21: error: cannot decompose inaccessible member ‘std::ranges::take_view<std::ranges::subrange<std::ranges::iota_view<char, int>::_Iterator, std::ranges::iota_view<char, int>::_Sentinel, std::ranges::subrange_kind::unsized> >::_M_base’ of ‘const std::ranges::take_view<std::ranges::subrange<std::ranges::iota_view<char, int>::_Iterator, std::ranges::iota_view<char, int>::_Sentinel, std::ranges::subrange_kind::unsized> >’
13 | for(const auto& [start, end] : r | std::views::chunk(3u)) {
| ^~~~~~~~~~~~
In file included from chunk.cpp:1:
/usr/include/c++/13/ranges:2153:11: note: declared private here
2153 | _Vp _M_base = _Vp();
| ^~~~~~~
I think that 'g' + 1 is causing integer promotion and is making the first and second parameter of iota() have different types. However, in other occasions, this seems fine. For example, this code works as expected:
#include <ranges>
#include <iostream>
int main() {
const auto r = std::views::iota('a', 'g' + 1);
for(const auto& val : r) {
std::cout << val << " ";
}
std::cout << "\n";
return 0;
}
What is happening here? And why does the error mentions (of all things) private members?
Since 'g' + 1 produces integer promotions, this makes views::iota('a', 'g' + 1) produce an iota_view whose iterator type is different from the sentinel type.
According to [range.iota.sentinel], the sentinel type can only be subtracted from the iterator type when sized_sentinel_for<W, Bound> is satisfied, which is not the case for the basic types char and int because they are not iterators.
In other words, this breaks iota_view's sentinel and iterator subtraction functionality:
auto r = views::iota('a', 'g' + 1);
r.begin() - r.end(); // error
When applying views::chunck to it, the resulting value type would be decltype(views::take(subrange(current_, end_), n_)) ([range.chunk.fwd.iter]), where current_ and end_ is the iterator and sentinel of this iota_view.
Since current_ and end_ no longer model sized_sentinel_for, CTAD will deduce a subrange with the third template parameter (subrange_kind) is unsized ([range.subrange.general]), and such a subrange is no longer sized_range:
auto r = views::iota('a', 'g' + 1);
auto s = ranges::subrange(r.begin(), r.end());
static_assert(ranges::sized_range<decltype(s)>); // failed
This makes views::take return a take_view object ([range.take.overview#2.5]), which cannot be structured binding.
For your original example, since the iterator type of views::iota('a', 'g') is the same as the sentinel type and the two can be subtracted, subrange(current_, end_) will still model sized_range and random_access_range. In this case, views::take will optimally return a subrange type that can be structured binding ([range.take.overview#2.2.3]).
This can arguably be seen as a defect in the standard, which is also LWG 3609.
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