This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of WP status.

3851. chunk_view::inner-iterator missing custom iter_move and iter_swap

Section: 26.7.28.5 [range.chunk.inner.iter] Status: WP Submitter: Hewill Kang Opened: 2023-01-06 Last modified: 2023-02-13

Priority: Not Prioritized

View all issues with WP 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 strings 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 Status changed: Voting → WP.]

Proposed resolution:

This wording is relative to N4917.

  1. Modify 26.7.28.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_).