Skip to content

Commit 2a0e7c6

Browse files
committed
New issue from Yaito Kakeyama & Nana Sakisaka: "concat_view::end() should be more constrained in order to support noncopyable iterators"
1 parent 0d5580c commit 2a0e7c6

File tree

1 file changed

+98
-0
lines changed

1 file changed

+98
-0
lines changed

xml/issue4166.xml

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
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 &amp; 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&lt;int&gt;{1, 2, 3};
17+
18+
std::stringstream ss{"4 5 6"};
19+
auto range_noncopyable_it = std::views::istream&lt;int&gt;(ss);
20+
21+
auto view1 = std::views::concat(range_copyable_it, range_noncopyable_it);
22+
static_assert(std::ranges::range&lt;decltype(view1)&gt;); // 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&lt;decltype(view2)&gt;); // <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&lt;const Views&gt; &amp;&amp; ...) &amp;&amp; <i>concatable</i>&lt;const Views...&gt;;
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&lt;iterator_t&lt;<i>maybe-const</i>&lt;<i>is-const</i>, Views&gt;&gt;&gt; &amp;&amp; ...) &amp;&amp;</ins>
86+
common_range&lt;<i>maybe-const</i>&lt;<i>is-const</i>, Views...[N - 1]&gt;&gt;) {
87+
return <i>iterator</i>&lt;<i>is-const</i>&gt;(this, in_place_index&lt;N - 1&gt;,
88+
ranges::end(std::get&lt;N - 1&gt;(<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

Comments
 (0)