Skip to content

Commit 6b8d2e3

Browse files
committed
New issue from Jiang An: "ranges::for_each possibly behaves differently from range-based for"
1 parent 191187a commit 6b8d2e3

File tree

1 file changed

+162
-0
lines changed

1 file changed

+162
-0
lines changed

xml/issue4389.xml

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
<?xml version='1.0' encoding='utf-8' standalone='no'?>
2+
<!DOCTYPE issue SYSTEM "lwg-issue.dtd">
3+
4+
<issue num="4389" status="New">
5+
<title>`ranges::for_each` possibly behaves differently from range-based `for`</title>
6+
<section><sref ref="[range.range]"/></section>
7+
<submitter>Jiang An</submitter>
8+
<date>28 Sep 2025</date>
9+
<priority>99</priority>
10+
11+
<discussion>
12+
<p>
13+
It was found in the blog post
14+
<a href="https://quuxplusone.github.io/blog/2024/12/09/foreach-versus-for/">
15+
"When `ranges::for_each` behaves differently from `for`"</a> that `ranges::for_each`
16+
can behave differently from range-based `for`, because
17+
</p>
18+
<ol>
19+
<li><p>`ranges::begin` and `ranges::end` possibly use different rules, i.e. one calls a member
20+
and the other calls an ADL-found non-member function, and</p></li>
21+
<li><p>these CPOs continue to perform ADL when a member `begin/end` is found but the
22+
function call is not valid, while the range-for stops and renders the program ill-formed.</p></li>
23+
</ol>
24+
<p>
25+
Perhaps the intent of Ranges was that the `ranges::range` concept should be stricter than
26+
plain range-for and all range types can be iterated via range-for with the same semantics
27+
as `ranges::for_each`. However, it seems very difficult (if not impossible) for a library
28+
implementation to tell whether a class has member `begin/end` but the corresponding member
29+
call is ill-formed with C++20 core language rules, and such determination is critical for
30+
eliminating the semantic differences between `ranges::for_each` and range-for.
31+
</p>
32+
</discussion>
33+
34+
<resolution>
35+
<p>
36+
This wording is relative to <paper num="N5014"/>.
37+
</p>
38+
39+
<blockquote class="note">
40+
<p>
41+
Two mutually exclusive resolutions are proposed here. One enforces semantic-identity checks,
42+
while the other doesn't and makes weird types satisfy but not model the range concept. I
43+
prefer the stricter one because the semantic-identity checks are fully static, but this probably
44+
requires compilers to add new intrinsics when reflection is absent.
45+
</p>
46+
</blockquote>
47+
48+
<p>
49+
<b>Option A</b>: (stricter)
50+
</p>
51+
52+
<ol>
53+
<li><p>Modify <sref ref="[range.access.begin]"/> as indicated:</p>
54+
55+
<blockquote>
56+
<p>
57+
-2- Given a subexpression `E` with type `T`, let `t` be an lvalue that denotes the reified object for `E`. Then:
58+
</p>
59+
<ol style="list-style-type: none">
60+
<li><p>(2.1) &mdash; [&hellip;]</p></li>
61+
<li><p>(2.2) &mdash; [&hellip;]</p></li>
62+
<li><p>(2.3) &mdash; [&hellip;]</p></li>
63+
<li><p>(2.4) &mdash; [&hellip;]</p></li>
64+
<li><p><ins>(2.?) &mdash; Otherwise, if <tt>remove_cvref_t&lt;T&gt;</tt> is a class type and search for
65+
`begin` in the scope of that class finds at least one declaration, `ranges::begin(E)` is ill-formed.</ins></p></li>
66+
<li><p>(2.5) &mdash; [&hellip;]</p></li>
67+
<li><p>(2.6) &mdash; Otherwise, `ranges::begin(E)` is ill-formed.</p></li>
68+
</ol>
69+
</blockquote>
70+
</li>
71+
72+
<li><p>Modify <sref ref="[range.access.end]"/> as indicated:</p>
73+
74+
<blockquote>
75+
<p>
76+
-2- Given a subexpression `E` with type `T`, let `t` be an lvalue that denotes the reified object for `E`. Then:
77+
</p>
78+
<ol style="list-style-type: none">
79+
<li><p>(2.1) &mdash; [&hellip;]</p></li>
80+
<li><p>(2.2) &mdash; [&hellip;]</p></li>
81+
<li><p>(2.3) &mdash; [&hellip;]</p></li>
82+
<li><p>(2.4) &mdash; [&hellip;]</p></li>
83+
<li><p>(2.5) &mdash; [&hellip;]</p></li>
84+
<li><p><ins>(2.?) &mdash; Otherwise, if <tt>remove_cvref_t&lt;T&gt;</tt> is a class type and search for
85+
`end` in the scope of that class finds at least one declaration, `ranges::end(E)` is ill-formed.</ins></p></li>
86+
<li><p>(2.6) &mdash; [&hellip;]</p></li>
87+
<li><p>(2.7) &mdash; Otherwise, `ranges::end(E)` is ill-formed.</p></li>
88+
</ol>
89+
</blockquote>
90+
</li>
91+
92+
<li><p>Modify <sref ref="[range.range]"/> as indicated:</p>
93+
94+
<blockquote>
95+
<p>
96+
-1- [&hellip;]
97+
</p>
98+
<pre>
99+
template&lt;class T&gt;
100+
concept range =
101+
requires(T&amp; t) {
102+
ranges::begin(t); // <i>sometimes equality-preserving (see below)</i>
103+
ranges::end(t);
104+
} <ins>&amp;&amp; <i>has-consistent-begin-end</i>&lt;T&gt;</ins>; <ins>// <i>see below</i></ins>
105+
</pre>
106+
<p>
107+
-2- [&hellip;]
108+
<p/>
109+
-3- [&hellip;]
110+
<p/>
111+
<ins>-?- <tt><i>has-consistent-begin-end</i>&lt;T&gt;</tt> is a constant expression of type `bool`,
112+
and it is `true` if and only if for the `t` introduced in the requires-expression above, either</ins>
113+
</p>
114+
<ol style="list-style-type: none">
115+
<li><p><ins>(?.1) &mdash; both `ranges::begin(t)` and `ranges::end(t)` are specified to select
116+
`auto(t.begin())` and `auto(t.end())` respectively, or</ins></p></li>
117+
<li><p><ins>(?.2) &mdash; both `ranges::begin(t)` and `ranges::end(t)` are specified not
118+
to select `auto(t.begin())` and `auto(t.end())` respectively.</ins></p></li>
119+
</ol>
120+
</blockquote>
121+
</li>
122+
</ol>
123+
124+
<p>
125+
<b>Option B</b>: (looser)
126+
</p>
127+
128+
<ol>
129+
<li><p>Modify <sref ref="[range.range]"/> as indicated:</p>
130+
131+
<blockquote>
132+
<p>
133+
-1- [&hellip;]
134+
</p>
135+
<pre>
136+
template&lt;class T&gt;
137+
concept range =
138+
requires(T&amp; t) {
139+
ranges::begin(t); // <i>sometimes equality-preserving (see below)</i>
140+
ranges::end(t);
141+
}
142+
</pre>
143+
<p>
144+
-2- Given an expression `t` such that `decltype((t))` is <tt>T&amp;</tt>, `T` models `range` only if
145+
</p>
146+
<ol style="list-style-type: none">
147+
<li><p>(2.1) &mdash; [&hellip;]</p></li>
148+
<li><p>(2.2) &mdash; [&hellip;]</p></li>
149+
<li><p>(2.3) &mdash; [&hellip;]</p></li>
150+
<li><p><ins>(2.?) &mdash; The range-based `for` statement <tt>for (auto&amp;&amp; x: t);</tt> is well-formed,
151+
and variable definitions <tt>auto <i>begin</i> = <i>begin-expr</i>;</tt> and <tt>auto <i>end</i> = <i>end-expr</i>;</tt>
152+
in the equivalent form (<sref ref="[stmt.ranged]"/>) of that statement are semantically equivalent to
153+
<tt>auto <i>begin</i> = ranges::begin(t);</tt> and <tt>auto <i>end</i> = ranges::end(t);</tt> respectively.</ins></p></li>
154+
</ol>
155+
156+
</blockquote>
157+
</li>
158+
</ol>
159+
160+
</resolution>
161+
162+
</issue>

0 commit comments

Comments
 (0)