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

3851. 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 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 Approved at February 2023 meeting in Issaquah. Status changed: Voting → WP.]

Proposed resolution:

This wording is relative to N4917.

  1. 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>>;
    
    
      };
    }
    
    […]