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.24.2 [range.zip.view], 26.7.26.2 [range.adjacent.view], 26.7.32.2 [range.cartesian.view] Status: New Submitter: Hewill Kang Opened: 2022-07-04 Last modified: 2023-02-07

Priority: 3

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

View all issues with New status.

Discussion:

Both zip_view::iterator's (26.7.24.3 [range.zip.iterator]) and adjacent_view::iterator's (26.7.26.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.24 [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.26 [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.24.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.26.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.24 [range.zip], zip view
      template<input_range... Views>
        requires (view<Views> && ...) && (sizeof...(Views) > 0) &&
                 (has-tuplable-ref<Views> && ...)
      class zip_view;
    
      […]
    
      // 26.7.26 [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.32 [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.24.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.24.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.24.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.26.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.26.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.26.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.32.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.32.3 [range.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 {
        […]
      };
    }