forked from villevoutilainen/papers
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrange-for.html
190 lines (167 loc) · 6.22 KB
/
range-for.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Relaxing the range-for loop customization point finding rules</title>
<style>
p {text-align:justify}
li {text-align:justify}
blockquote.note
{
background-color:#E0E0E0;
padding-left: 15px;
padding-right: 15px;
padding-top: 1px;
padding-bottom: 1px;
}
ins {color:#00A000}
del {color:#A00000}
</style>
</head>
<body>
<address align=right>
Document number: D????
<br/>
Audience: EWG
<br/>
<br/>
<a href="mailto:ville.voutilainen@gmail.com">Ville Voutilainen</a><br/>
2018-02-11<br/>
</address>
<hr/>
<h1 align=center>Relaxing the range-for loop customization point finding rules</h1>
<h2>Abstract</h2>
<p>
This paper proposes relaxing the rules for finding a customization
point for a range-for loop; that is, just because either
of a member begin() or end() is found, that should not disable
an ADL customization point. This allows writing ADL begin()/end()
for types that happen to have an end() member function in their
class hierarchy. Examples of such types are standard streams,
and user wrappers that happen to inherit from standard streams.
</p>
<p>
The proposal in this paper is that, if _both_ member begin() and
end() are found, then the members are used in range-for. If just
one of them is found, members are not used, but the lookup
continues to try and find an ADL begin()/end() pair. Note that
this paper does *not* propose operating with a mixed member/non-member
begin()/end() pair.
</p>
<h2>Rationale</h2>
<p>
It would seem perfectly reasonable to be able to simply
range-for-loop over the data of an input stream. The lack
of support for that in the standard might not be such
a huge surprise, but what may well be surprising is that
we have currently painted ourselves into a corner where such
support can't be added. In [ios::seekdir], we specify
a seekdir enumerator seekdir::end, which range-for finds,
and subsequently commits to using members for range-for
traversal of an istream.
</p>
<p>
Furthermore, programmers might want to wrap istreams by
deriving from them. We may wish to advice against that,
but that doesn't, to me, constitute a sufficient reason
to forever turn off range-traversal for such wrapper types.
In particular, veneer types are seemingly a perfectly
reasonable technique that uses inheritance. See the good
old <a href="synesis.com.au/resources/articles/cpp/veneers.pdf">synesis.com.au/resources/articles/cpp/veneers.pdf</a>.
</p>
<h2>Rumination</h2>
<p>
So, first of all, are the current rules really wrong? Well, when
we put finishing touches on range-for at the very end of C++0x,
the current lookup rules were decided on, in Madrid 2011. They
were based on <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3257.pdf">N3257</a>. In that paper, Jonathan Wakely makes a somewhat
convincing case for preferring an ill-formed program if a user
provides just a begin() but no end(), and there happens to
be a namespace-scope template that will then be used. That rationale
was certainly enough to convince the committee at that time.
</p>
<p>
However, that rationale relies on the assumption that the user
somehow intended to opt in to range-for traversal, failed to do
so correctly, and is now surprised that some namespace-scope
begin()/end() pair was chosen. The problem here is that
if the user instead intended to write such a namespace-scope
begin()/end() pair, and can't change the presence of an 'end'
in his class hierarchy, that's just impossible to do.
</p>
<p>
While it was a nice idea to avoid funny results in the examples
depicted in N3257, I think it's an unfortunate consequence
that we now prevent perfectly valid use cases written by programmers
who know what they are doing. We shouldn't base our rules on
overt worrying about things that could go wrong, but rather
allow useful programming techniques to be applied, and rely
on tools to hold the hand of a programmer. That is, we used
to have a principle of trusting the programmer rather than
molly-coddling him.
</p>
<p>
Is this a significant problem? Probably not, not something
that a whole lot of programmers run into. However, the problem
seems easy to fix, with not much downside. We have more cases
where customization point lookup rules are draconian and prevent
reasonable code from working; I have written another paper that deals with
a customization point lookup problem with structured bindings. These
problems can be worked around with adapter types, but I'd rather
have less draconian rules.
</p>
<h2>An example of code that doesn't work</h2>
<p>
<pre>
<code>
#include <sstream>
#include <iterator>
struct X : std::stringstream
{
// some other stuff here
};
std::istream_iterator<char> begin(X& x)
{
return std::istream_iterator<char>(x);
}
std::istream_iterator<char> end(X& x)
{
return std::istream_iterator<char>();
}
int main()
{
X x;
for (auto&& i : x) {
// do your magic here
}
}
</code>
</pre>
</p>
<p>
I find it very unreasonable that that code is ill-formed.
N3257 suggests using an adapter wrapper as a work-around,
so that X would be wrapped into another class which does not
inherit from stringstream, and can then cleanly provide
the ADL begin()/end() customization points. While that's doable,
it's also a fairly heavy-handed approach, especially if
this X-wrapper has no other purpose than providing range access.
Furthermore, the X-wrapper isn't substitutable for X, since
the whole idea of public inheritance of stringstream in X is
that there's a standard conversion from X to stringstream; there
is none from X-wrapper to stringstream.
</p>
<h2>Wording</h2>
<p>
In [stmt.ranged]/1, bullet 1.3.2, edit as follows:
</p>
<p>
<blockquote>
if the for-range-initializer is an expression of class type C, the unqualified-ids begin and end are
looked up in the scope of C as if by class member access lookup (6.4.5), and if <del>either (or</del> both<del>)</del>
find<del>s</del> at least one declaration, begin-expr and end-expr are __range.begin() and __range.end(),
respectively;
</blockquote>
</p>
</body>
</html>