Title
zip_view and adjacent_view are underconstrained
Status
new
Section
[range.zip.view][range.adjacent.view]
Submitter
Hewill Kang

Created on 2022-07-04.00:00:00 last changed 1 month ago

Messages

Date: 2022-07-10.09:27:26

Proposed resolution:

This wording is relative to N4910.

  1. Modify [ranges.syn], header <ranges> synopsis, as indicated:

    namespace std::ranges {
      […]
      // [range.zip], zip view
      template<class Ref>
        concept tuple-constructible-reference = see below; // exposition only
    
      template<input_range... Views>
        requires (view<Views> && ...) && (sizeof...(Views) > 0) &&
                 (tuple-constructible-reference<range_reference_t<Views>> && ...)
      class zip_view;
    
      […]
    
      // [range.adjacent], adjacent view
      template<forward_range V, size_t N>
        requires view<V> && (N > 0) && 
                 tuple-constructible-reference<range_reference_t<V>>
      class adjacent_view;
    }
    […]
    
  2. Modify [range.zip.view] as indicated:

    namespace std::ranges {
      template<class Ref>
        concept tuple-constructible-reference =            // exposition only
          is_reference_v<Ref> || move_constructible<Ref>;
      […]
      template<input_range... Views>
        requires (view<Views> && ...) && (sizeof...(Views) > 0) &&
                 (tuple-constructible-reference<range_reference_t<Views>> && ...)
      class zip_view : public view_interface<zip_view<Views...>> {
        […]
      };
      […]
    }
    
  3. Modify [range.adjacent.view] as indicated:

    namespace std::ranges {
      template<forward_range V, size_t N>
        requires view<V> && (N > 0) && 
                 tuple-constructible-reference<range_reference_t<V>>
      class adjacent_view : public view_interface<adjacent_view<V, N>> {
        […]
      };
      […]
    }
    
Date: 2022-07-04.00:00:00

Both zip_view::iterator's ([range.zip.iterator]) and adjacent_view::iterator's ([range.adjacent.iterator]) operator* have similar Effects: elements:

return tuple-transform([](auto& i) -> decltype(auto) { return *i; }, current_);

where tuple-transform is defined as:

template<class F, class Tuple>
constexpr auto tuple-transform(F&& f, Tuple&& tuple) { // exposition only
  return apply([&]<class... Ts>(Ts&&... elements) {
    return tuple-or-pair<invoke_result_t<F&, Ts>...>(
      invoke(f, std::forward<Ts>(elements))...
    );
  }, std::forward<Tuple>(tuple));
}

That is, zip_view::iterator will invoke the operator* of each iterator of Views and return a tuple containing its reference.

This is not a problem when the reference of iterators is actually the reference type. However, when the operator* returns a prvalue of non-movable type, tuple-transform will be ill-formed since there are no suitable constructors for tuple:

#include <ranges>

struct NonMovable {
  NonMovable() = default;
  NonMovable(NonMovable&&) = delete;
};

auto r = std::views::iota(0, 5)
       | std::views::transform([](int) { return NonMovable{}; });
auto z = std::views::zip(r);
auto f = *z.begin(); // hard error

We should constrain the range_reference_t of the underlying range to be move_constructible when it is not a reference type, which also solves similar issues in zip_view::iterator and adjacent_view::iterator's operator[] and iter_move.

History
Date User Action Args
2022-07-10 09:27:26adminsetmessages: + msg12570
2022-07-04 00:00:00admincreate