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

### 3731. `zip_view` and `adjacent_view` are underconstrained

Section: 26.7.23.2 [range.zip.view], 26.7.25.2 [range.adjacent.view], 26.7.31.2 [range.cartesian.view] Status: New Submitter: Hewill Kang Opened: 2022-07-04 Last modified: 2022-09-25

Priority: 3

View all other issues in [range.zip.view].

View all issues with New status.

Discussion:

Both `zip_view::iterator`'s (26.7.23.3 [range.zip.iterator]) and `adjacent_view::iterator`'s (26.7.25.3 [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`.

[2022-08-23; Reflector poll]

Set priority to 3 after reflector poll. "The constraint should just be `move_constructible`."

Previous resolution [SUPERSEDED]:

> This wording is relative to N4910.

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

>    >     namespace std::ranges {
>    >       […]
>    >       // 26.7.23 [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;

>    >       […]

>    >       // 26.7.25 [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 26.7.23.2 [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 26.7.25.2 [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>> {
>    >         […]
>    >       };
>    >       […]
>    >     }

[2022-09-25; Hewill provides improved wording]

Proposed resolution:

This wording is relative to N4917.

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

   >     namespace std::ranges {
   >       […]

   >       template<class R>
   >         concept has-tuplable-ref =            // exposition only
   >           move_constructible<range_reference_t<R>>;

   >       // 26.7.23 [range.zip], zip view
   >       template<input_range... Views>
   >         requires (view<Views> && ...) && (sizeof...(Views) > 0) &&
   >                  (has-tuplable-ref<Views> && ...)
   >       class zip_view;

   >       […]

   >       // 26.7.25 [range.adjacent], adjacent view
   >       template<forward_range V, size_t N>
   >         requires view<V> && (N > 0) && has-tuplable-ref<V>
   >       class adjacent_view;

   >       […]

   >       // 26.7.31 [range.cartesian], cartesian product view
   >       template<input_range First, forward_range... Vs>
   >         requires (view<First> && ... && view<Vs>) &&
   >                  (has-tuplable-ref<First> && ... && has-tuplable-ref<Vs>)
   >       class cartesian_product_view;

   >       […]
   >     }

2. Modify 26.7.23.2 [range.zip.view] as indicated:

   >     namespace std::ranges {
   >       […]

   >       template<input_range... Views>
   >         requires (view<Views> && ...) && (sizeof...(Views) > 0) &&
   >                  (has-tuplable-ref<Views> && ...)
   >       class zip_view : public view_interface<zip_view<Views...>> {
   >         […]
   >       };
   >     }

3. Modify 26.7.23.3 [range.zip.iterator] as indicated:

   >     namespace std::ranges {
   >       […]
   >       template<input_range... Views>
   >         requires (view<Views> && ...) && (sizeof...(Views) > 0) &&
   >                  (has-tuplable-ref<Views> && ...)
   >       template<bool Const>
   >       class zip_view<Views...>::iterator {
   >         […]
   >       };
   >     }

4. Modify 26.7.23.4 [range.zip.sentinel] as indicated:

   >     namespace std::ranges {
   >       template<input_range... Views>
   >         requires (view<Views> && ...) && (sizeof...(Views) > 0) &&
   >                  (has-tuplable-ref<Views> && ...)
   >       template<bool Const>
   >       class zip_view<Views...>::sentinel {
   >         […]
   >       };
   >     }

5. Modify 26.7.25.2 [range.adjacent.view] as indicated:

   >     namespace std::ranges {
   >       template<forward_range V, size_t N>
   >         requires view<V> && (N > 0) && has-tuplable-ref<V>
   >       class adjacent_view : public view_interface<adjacent_view<V, N>> {
   >         […]
   >       };
   >     }

6. Modify 26.7.25.3 [range.adjacent.iterator] as indicated:

   >     namespace std::ranges {
   >       template<forward_range V, size_t N>
   >         requires view<V> && (N > 0) && has-tuplable-ref<V>
   >       template<bool Const>
   >       class adjacent_view<V, N>::iterator {
   >         […]
   >       };
   >     }

7. Modify 26.7.25.4 [range.adjacent.sentinel] as indicated:

   >     namespace std::ranges {
   >       template<forward_range V, size_t N>
   >         requires view<V> && (N > 0) && has-tuplable-ref<V>
   >       template<bool Const>
   >       class adjacent_view<V, N>::sentinel {
   >         […]
   >       };
   >     }

8. Modify 26.7.31.2 [range.cartesian.view] as indicated:

   >     namespace std::ranges {
   >       […]
   >       template<input_range First, forward_range... Vs>
   >         requires (view<First> && ... && view<Vs>) &&
   >                  (has-tuplable-ref<First> && ... && has-tuplable-ref<Vs>)
   >       class cartesian_product_view : public view_interface<cartesian_product_view<First, Vs...>> {
   >         […]
   >       };
   >     }

9. Modify 26.7.31.3 [ranges.cartesian.iterator] as indicated:

   >     namespace std::ranges {
   >       template<input_range First, forward_range... Vs>
   >         requires (view<First> && ... && view<Vs>) &&
   >                  (has-tuplable-ref<First> && ... && has-tuplable-ref<Vs>)
   >       template<bool Const>
   >       class cartesian_product_view<First, Vs...>::iterator {
   >         […]
   >       };
   >     }