Created on 2022-07-04.00:00:00 last changed 1 month ago
Proposed resolution:
This wording is relative to N4910.
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; } […]
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...>> { […] }; […] }
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>> { […] }; […] }
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:26 | admin | set | messages: + msg12570 |
2022-07-04 00:00:00 | admin | create |