|
| 1 | +<?xml version='1.0' encoding='utf-8' standalone='no'?> |
| 2 | +<!DOCTYPE issue SYSTEM "lwg-issue.dtd"> |
| 3 | + |
| 4 | +<issue num="4166" status="New"> |
| 5 | +<title>`concat_view::end()` should be more constrained in order to support noncopyable iterators</title> |
| 6 | +<section><sref ref="[range.concat.view]"/></section> |
| 7 | +<submitter>Yaito Kakeyama & Nana Sakisaka</submitter> |
| 8 | +<date>13 Oct 2024</date> |
| 9 | +<priority>99</priority> |
| 10 | + |
| 11 | +<discussion> |
| 12 | +<p> |
| 13 | +There is a case that `concat(a, b)` compiles but `concat(b, a)` does not. |
| 14 | +</p> |
| 15 | +<blockquote><pre> |
| 16 | +auto range_copyable_it = std::vector<int>{1, 2, 3}; |
| 17 | + |
| 18 | +std::stringstream ss{"4 5 6"}; |
| 19 | +auto range_noncopyable_it = std::views::istream<int>(ss); |
| 20 | + |
| 21 | +auto view1 = std::views::concat(range_copyable_it, range_noncopyable_it); |
| 22 | +static_assert(std::ranges::range<decltype(view1)>); // ok |
| 23 | +assert(std::ranges::equal(view1, std::vector{1, 2, 3, 4, 5, 6})); // ok |
| 24 | + |
| 25 | +auto view2 = std::views::concat(range_noncopyable_it, range_copyable_it); |
| 26 | +// static_assert(std::ranges::range<decltype(view2)>); // <span style="color:#C80000;font-weight:bolder">error</span> |
| 27 | +// assert(std::ranges::equal(view2, std::vector{4, 5, 6, 1, 2, 3})); // <span style="color:#C80000;font-weight:bolder">error</span> |
| 28 | +</pre></blockquote> |
| 29 | +<p> |
| 30 | +The reason behind this is as follows: |
| 31 | +<p/> |
| 32 | +Firstly, if all `Views...` satisfy the `std::ranges::range` concept, then `concat_view` should also satisfy it. |
| 33 | +However, if any of the `Views...` have a noncopyable iterator and the last view is `common_range`, the current |
| 34 | +`concat_view` fails to model a range. |
| 35 | +<p/> |
| 36 | +For `concat_view` to model a range, its sentinel must satisfy `std::semiregular`, but `concat_view::end()` |
| 37 | +returns a `concat_view::iterator`, which is noncopyable if the underlying iterator is noncopyable. This |
| 38 | +issue arises from the proposed implementation where the iterator uses `std::variant`. Although this |
| 39 | +specification is exposition-only, even if an alternative type-erasure mechanism is used, copying is still |
| 40 | +required if the user attempts to copy an iterator. |
| 41 | +<p/> |
| 42 | +To resolve the issue, `concat_view::end()` can and should fallback to returning `std::default_sentinel` |
| 43 | +in such cases. |
| 44 | +<p/> |
| 45 | +Unfortunately, as a side effect, this fix would prevent `concat_view` from being a `common_range` in certain |
| 46 | +situations. According to <paper num="P2542R8"/>: |
| 47 | +</p> |
| 48 | +<blockquote style="border-left: 3px solid #ccc;padding-left: 15px;"> |
| 49 | +<p> |
| 50 | +`concat_view` can be `common_range` if the last underlying range models `common_range` |
| 51 | +</p> |
| 52 | +</blockquote> |
| 53 | +<p> |
| 54 | +However, this is no longer true after applying our fix. That said, these two issues cannot be resolved |
| 55 | +simultaneously due to implementability. Therefore, we suggest applying our fix regardless and accepting |
| 56 | +that `concat_view` will not always inherit `common_range`. Note that the current draft (<paper num="N4988"/>) |
| 57 | +does not explicitly specify when `concat_view` can model `common_range`, so no addition is required for |
| 58 | +mentioning this point. |
| 59 | +<p/> |
| 60 | +A similar issue had been reported as <iref ref="3385"/>, which was eventually adopted as a C++20 DR. This |
| 61 | +DR indicates that LWG approved the decision to require `copyable` in order to model a `common_iterator`. |
| 62 | +</p> |
| 63 | +</discussion> |
| 64 | + |
| 65 | +<resolution> |
| 66 | +<p> |
| 67 | +This wording is relative to <paper num="N4993"/>. |
| 68 | +</p> |
| 69 | + |
| 70 | +<ol> |
| 71 | +<li><p>Modify <sref ref="[range.concat.view]"/> as indicated:</p> |
| 72 | + |
| 73 | +<blockquote> |
| 74 | +<pre> |
| 75 | +constexpr auto end() const |
| 76 | + requires (range<const Views> && ...) && <i>concatable</i><const Views...>; |
| 77 | +</pre> |
| 78 | +<blockquote> |
| 79 | +<p> |
| 80 | +-7- <i>Effects</i>: Let <tt><i>is-const</i></tt> be `true` for the const-qualified overload, and `false` |
| 81 | +otherwise. Equivalent to: |
| 82 | +</p> |
| 83 | +<blockquote><pre> |
| 84 | +constexpr auto N = sizeof...(Views); |
| 85 | +if constexpr (<ins>(semiregular<iterator_t<<i>maybe-const</i><<i>is-const</i>, Views>>> && ...) &&</ins> |
| 86 | + common_range<<i>maybe-const</i><<i>is-const</i>, Views...[N - 1]>>) { |
| 87 | + return <i>iterator</i><<i>is-const</i>>(this, in_place_index<N - 1>, |
| 88 | + ranges::end(std::get<N - 1>(<i>views_</i>))); |
| 89 | +} else { |
| 90 | + return default_sentinel; |
| 91 | +} |
| 92 | +</pre></blockquote> |
| 93 | +</blockquote> |
| 94 | +</blockquote> |
| 95 | +</li> |
| 96 | +</ol></resolution> |
| 97 | + |
| 98 | +</issue> |
0 commit comments