Skip to content

Commit

Permalink
New issue from S.B. Tam: "§[range.utility.conv.to] ranges::to may cau…
Browse files Browse the repository at this point in the history
…se infinite recursion if range_value_t<C> is a non-move-constructible range"
  • Loading branch information
Dani-Hub committed Nov 18, 2023
1 parent 4407480 commit 7e556b8
Showing 1 changed file with 147 additions and 0 deletions.
147 changes: 147 additions & 0 deletions xml/issue4008.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
<?xml version='1.0' encoding='utf-8' standalone='no'?>
<!DOCTYPE issue SYSTEM "lwg-issue.dtd">

<issue num="4008" status="New">
<title>&sect;[range.utility.conv.to] <tt>ranges::to</tt> may cause infinite recursion if <tt>range_value_t&lt;C&gt;</tt>
is a non-move-constructible range</title>
<section><sref ref="[range.utility.conv.to]"/></section>
<submitter>S. B. Tam</submitter>
<date>08 Nov 2023</date>
<priority>99</priority>

<discussion>
<p>
<sref ref="[range.utility.conv.to]"/>/2 says:
</p>
<blockquote>
<ol style="list-style-type: none">
<li><p>(2.1) &mdash; If <tt>C</tt> does not satisfy <tt>input_range</tt> or
<tt>convertible_to&lt;range_reference_t&lt;R&gt;, range_value_t&lt;C&gt;&gt;</tt> is <tt>true</tt>:</p>
<ol style="list-style-type: none">
<li><p>[&hellip;]</p></li>
</ol></li>
<li><p>(2.2) &mdash; Otherwise, if <tt>input_range&lt;range_reference_t&lt;R&gt;&gt;</tt> is <tt>true</tt>:
</p>
<blockquote>
<pre>
to&lt;C&gt;(r | views::transform([](auto&amp;&amp; elem) {
return to&lt;range_value_t&lt;C&gt;&gt;(std::forward&lt;decltype(elem)&gt;(elem));
}), std::forward&lt;Args&gt;(args)...);
</pre>
</blockquote>
</li>
<li><p>[&hellip;]</p></li>
</ol>
</blockquote>
<p>
That is, if <tt>range_reference_t&lt;R&gt;</tt> is not convertible to <tt>range_value_t&lt;C&gt;</tt>, and
<tt>range_reference_t&lt;R&gt;</tt> is an input range, <tt>views::transform</tt> is applied to convert the
inner range to <tt>range_value_t&lt;C&gt;</tt> (through <tt>to&lt;range_value_t&lt;C&gt;&gt;</tt>), and then
the transformed range is converted to <tt>C</tt> (through <tt>to&lt;C&gt;</tt>).
<p/>
Consider:
</p>
<blockquote><pre>
#include &lt;ranges&gt;

struct ImmovableRange {
ImmovableRange(int*, int*);
ImmovableRange(ImmovableRange&amp;&amp;) = delete;

int* begin();
int* end();
};

struct C {
ImmovableRange* begin();
ImmovableRange* end();
};

using R = int[1][2];

void test() {
(void)std::ranges::to&lt;C&gt;(R{});
}
</pre></blockquote>
<p>
Here:
</p>
<ol>
<li><p><tt>convertible_to&lt;range_reference_t&lt;R&gt;, range_value_t&lt;C&gt;&gt;</tt> is <tt>false</tt>.</p></li>
<li><p><tt>range_reference_t&lt;R&gt;</tt> satisfies <tt>input_range</tt>.</p></li>
<li><p><tt>range_reference_t&lt;R&gt;</tt> can be converted to <tt>range_value_t&lt;C&gt;</tt> through
<tt>to&lt;range_value_t&lt;C&gt;&gt;</tt>. (If it couldn't, an error would be produced immediately.)</p></li>
</ol>
<p>
So <tt>to&lt;C&gt;</tt> is called recursively, constructing <tt>C</tt> with the transformed range (whose
<tt>range_reference_t&lt;R&gt;</tt> is the same as <tt>range_value_t&lt;C&gt;</tt>). For the construction
from the transformed range:
</p>
<ol>
<li><p><tt>range_reference_t&lt;R&gt;</tt> and <tt>range_value_t&lt;C&gt;</tt> are both <tt>ImmovableRange</tt>.</p></li>
<li><p><tt>convertible_to&lt;range_reference_t&lt;R&gt;, range_value_t&lt;C&gt;&gt;</tt> (i.e.
<tt>convertible_to&lt;ImmovableRange, ImmovableRange&gt;</tt>) is <tt>false</tt>.</p></li>
<li><p><tt>range_reference_t&lt;R&gt;</tt> (i.e. <tt>ImmovableRange</tt>) satisfies <tt>input_range</tt>.</p></li>
<li><p><tt>range_reference_t&lt;R&gt;</tt> can be converted to <tt>range_value_t&lt;C&gt;</tt> through
<tt>to&lt;range_value_t&lt;C&gt;&gt;</tt>.</p></li>
</ol>
<p>
So <tt>to&lt;C&gt;</tt> is called recursively again, transforming the range for the second time. This time,
the transformation does not change any of the above four facts. As a result, <tt>to&lt;C&gt;</tt> is called
yet again, leading to an infinite recursion.
<p/>
I believe this can be fixed by stop calling <tt>to&lt;C&gt;</tt> recursively when <tt>range_reference_t&lt;R&gt;</tt>
is the same as <tt>range_value_t&lt;C&gt;</tt>.
</p>
</discussion>

<resolution>
<p>
This wording is relative to <paper num="N4964"/>.
</p>

<ol>

<li><p>Modify <sref ref="[range.utility.conv.to]"/> as indicated:</p>

<blockquote>
<pre>
template&lt;class C, input_range R, class... Args&gt; requires (!view&lt;C&gt;)
constexpr C to(R&amp;&amp; r, Args&amp;&amp;... args);
</pre>
<blockquote>
<p>
-1- <i>Mandates</i>: <tt>C</tt> is a cv-unqualified class type.
</p>
<p>
-2- <i>Returns</i>: An object of type <tt>C</tt> constructed from the elements of <tt>r</tt> in the following manner:
</p>
<ol style="list-style-type: none">
<li><p>(2.1) &mdash; If <tt>C</tt> does not satisfy <tt>input_range</tt> or
<tt>convertible_to&lt;range_reference_t&lt;R&gt;, range_value_t&lt;C&gt;&gt;</tt> is <tt>true</tt>:</p>
<ol style="list-style-type: none">
<li><p>[&hellip;]</p></li>
</ol></li>
<li><p>(2.2) &mdash; Otherwise, if <ins><tt>same_as&lt;range_reference_t&lt;R&gt;, range_value_t&lt;C&gt;&gt;</tt>
is <tt>false</tt> and</ins> <tt>input_range&lt;range_reference_t&lt;R&gt;&gt;</tt> is <tt>true</tt>:
</p>
<blockquote>
<pre>
to&lt;C&gt;(r | views::transform([](auto&amp;&amp; elem) {
return to&lt;range_value_t&lt;C&gt;&gt;(std::forward&lt;decltype(elem)&gt;(elem));
}), std::forward&lt;Args&gt;(args)...);
</pre>
</blockquote>
</li>
<li><p>(2.3) &mdash; Otherwise, the program is ill-formed.</p></li>
</ol>
</blockquote>
</blockquote>


</li>

</ol>
</resolution>

</issue>

0 comments on commit 7e556b8

Please sign in to comment.