chunk_view::inner-iterator
missing custom iter_move
and iter_swap
Section: 25.7.29.5 [range.chunk.inner.iter] Status: C++23 Submitter: Hewill Kang Opened: 2023-01-06 Last modified: 2023-11-22
Priority: Not Prioritized
View all issues with C++23 status.
Discussion:
For the input version of chunk_view
,
its inner-iterator::operator*()
simply
dereferences the underlying input iterator.
However, there are no customized iter_move
and iter_swap
overloads for the inner-iterator
,
which prevents customized behavior when applying views::chunk
to a range whose iterator type is a proxy iterator,
for example:
#include <algorithm>
#include <cassert>
#include <ranges>
#include <sstream>
#include <vector>
int main() {
auto ints = std::istringstream{"0 1 2 3 4"};
std::vector<std::string> vs{"the", "quick", "brown", "fox"};
auto r = std::views::zip(vs, std::views::istream<int>(ints))
| std::views::chunk(2)
| std::views::join;
std::vector<std::tuple<std::string, int>> res;
std::ranges::copy(std::move_iterator(r.begin()), std::move_sentinel(r.end()),
std::back_inserter(res));
assert(vs.front().empty()); // assertion failed
}
zip
iterator has a customized iter_move
behavior,
but since there is no iter_move
specialization for
inner-iterator
,
when we try to move elements in chunk_view
,
move_iterator
will fallback to use the default implementation of
iter_move
, making string
s not moved as expected
from the original vector
but copied instead.
[2023-02-01; Reflector poll]
Set status to Tentatively Ready after five votes in favour during reflector poll.
[2023-02-13 Approved at February 2023 meeting in Issaquah. Status changed: Voting → WP.]
Proposed resolution:
This wording is relative to N4917.
Modify 25.7.29.5 [range.chunk.inner.iter] as indicated:
[…]namespace std::ranges { template<view V> requires input_range<V> class chunk_view<V>::inner-iterator { chunk_view* parent_; // exposition only constexpr explicit inner-iterator(chunk_view& parent) noexcept; // exposition only public: […] friend constexpr difference_type operator-(default_sentinel_t y, const inner-iterator& x) requires sized_sentinel_for<sentinel_t<V>, iterator_t<V>>; friend constexpr difference_type operator-(const inner-iterator& x, default_sentinel_t y) requires sized_sentinel_for<sentinel_t<V>, iterator_t<V>>; friend constexpr range_rvalue_reference_t<V> iter_move(const inner-iterator& i) noexcept(noexcept(ranges::iter_move(*i.parent_->current_))); friend constexpr void iter_swap(const inner-iterator& x, const inner-iterator& y) noexcept(noexcept(ranges::iter_swap(*x.parent_->current_, *y.parent_->current_))) requires indirectly_swappable<iterator_t<V>>; }; }friend constexpr range_rvalue_reference_t<V> iter_move(const inner-iterator& i) noexcept(noexcept(ranges::iter_move(*i.parent_->current_)));-?- Effects: Equivalent to:
return ranges::iter_move(*i.parent_->current_);
friend constexpr void iter_swap(const inner-iterator& x, const inner-iterator& y) noexcept(noexcept(ranges::iter_swap(*x.parent_->current_, *y.parent_->current_))) requires indirectly_swappable<iterator_t<V>>;-?- Effects: Equivalent to:
ranges::iter_swap(*x.parent_->current_, *y.parent_->current_)
.