forked from HowardHinnant/papers
-
Notifications
You must be signed in to change notification settings - Fork 5
/
ruminations-on-lambda-captures.html
396 lines (374 loc) · 11.7 KB
/
ruminations-on-lambda-captures.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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Ruminations on lambda captures</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/>
<br/>
<a href="mailto:[email protected]">Ville Voutilainen</a><br/>
2016-01-28<br/>
</address>
<hr/>
<h1 align=center>Ruminations on lambda captures</h1>
<h2>Abstract</h2>
<p>
The proposal for capturing *this by value (<a href="http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/p0018r1.html">P0018</a>) raised suggestions
for a "true value capture", which led to suggestions to change capture-default
that defaults to by-value capture([=]) in the case of capturing class members.
This paper explores what the suggested
changes to the capture-default might mean. This paper specifically doesn't
try to claim that any of the changes would have an effect on any particular
amount of existing code, and admits that the examples in this paper are
somewhat concocted and for illustrative purposes only.
</p>
<h2>Contents</h2>
<ul>
<li><a href="#MainVariants">The main suggestions of changed semantics for capturing members</a></li>
<li><a href="#WhyChange">Why change anything?</a></li>
<li><a href="#EffectOfFullCopy">The effect of copying the whole object</a></li>
<li><a href="#EffectOfMemberCopy">The effect of copying individual members</a></li>
<li><a href="#WhatAboutNonMemberLambdas">Sure, but aren't such issues already present in lambdas outside classes or class member definitions?</a></li>
<li><a href="#ExplicitFullObjectCopy">Does the proposed [*this] suffer from these problems?</a></li>
<li><a href="#CopyingMembersIsHard">"Copying members is too hard."</a></li>
<li><a href="#WhatToDo">""Ok, hotshot, what do you recommend?"</a></li>
</ul>
<a name="MainVariants"/><h2>The main suggestions of changed semantics for capturing members</h2>
<p>
There have been two suggestions for how the semantics of the capture-default
for by-value capture of members should change:
</p>
<p>
<ol>
<li>Capture the whole object by value.</li>
<li>Capture the individual members by value.</li>
</ol>
</p>
<p>
The first suggestion means roughly the following:
</p>
<p>
<pre>
<code>
struct X
{
int y;
void f()
{
auto lam = [=](){
bar(y); // X::y is used in the lambda, so the whole object
// is copied and the X::y of the copy is used here
};
// use lam any which way
}
};
</code>
</pre>
</p>
<p>
The second suggestion means roughly the following:
</p>
<p>
<pre>
<code>
struct X
{
int y;
void f()
{
auto lam = [=](){
bar(y); // X::y is used in the lambda, so X::y
// is copied and the copy of that single member is used here
};
// use lam any which way
}
};
</code>
</pre>
</p>
<a name="WhyChange"/><h2>Why change anything?</h2>
<p>
The motivation for change is two-fold:
</p>
<p>
<ol>
<li>A by-value capture-default copies every entity mentioned in the
lambda-body, except if the entities are members. This presents
a consistency argument between lambdas inside classes and lambdas
outside classes or class member definitions.
</li>
<li>In addition to a mere consistency argument, a by-value capture-default
not performing an actual object copy (it will copy the pointer 'this',
not the object pointed to nor its members) is suggested to be a pitfall.
</li>
</ol>
</p>
<a name="EffectOfFullCopy"/><h2>The effect of copying the whole object</h2>
<p>
If the whole surrounding object is copied when a by-value capture-default
is used inside a class, we are potentially looking at making currently
valid code ill-formed, or changing the semantics of existing code silently.
</p>
<p>
</p>
<p>
Here's one example of making currently valid code ill-formed:
</p>
<p>
<pre>
<code>
struct X
{
unique_ptr<int> y;
void f()
{
auto lam = [=](){
bar(y); // *this is attempted to be copied, but that's ill-formed
// because X is move-only.
};
// use lam any which way
}
};
</code>
</pre>
</p>
<p>
Here's another example of making currently valid code ill-formed:
</p>
<p>
<pre>
<code>
struct X
{
mutex y;
void f()
{
auto lam = [=](){
bar(y); // *this is attempted to be copied, but that's ill-formed
// because X is neither movable nor copyable.
};
// use lam any which way
}
};
</code>
</pre>
</p>
<p>
Here's one example of a silent change of semantics:
</p>
<p>
<pre>
<code>
struct X
{
vector<int> y;
void f()
{
auto lam = [=](){
bar(y); // *this is copied, and X::y with it.
// This introduces a copy that wasn't there before,
// and means that any code that refers to y will
// now refer to the X::y of a copied object, not
// the original X::y.
};
// use lam any which way
}
};
</code>
</pre>
</p>
<p>
The extra copy can be a performance issue. The change in which
X::y is referred may be a correctness issue.
</p>
<a name="EffectOfMemberCopy"/><h2>The effect of copying individual members</h2>
<p>
As with copying a full object, copying individual members has similar
potentials for breakage; it can make currently valid code ill-formed,
or change semantics silently.
</p>
<p>
Here's a slight modification of the move-only example where code
becomes ill-formed:
</p>
<p>
<pre>
<code>
struct X
{
unique_ptr<int> y;
void f()
{
auto lam = [=](){
bar(y); // X::y is attempted to be copied, but that's ill-formed
// because X::y is move-only.
};
// use lam any which way
}
};
</code>
</pre>
</p>
<p>
In a similar vein, the noncopyable/nonmovable case also breaks:
</p>
<p>
<pre>
<code>
struct X
{
mutex y;
void f()
{
auto lam = [=](){
bar(y); // X::y is attempted to be copied, but that's ill-formed
// because X::y is neither movable nor copyable.
};
// use lam any which way
}
};
</code>
</pre>
</p>
<p>
The previous example for a silent change of semantics has the same issues.
However, there are different examples:
</p>
<p>
<pre>
<code>
struct X
{
vector<int> y;
vector<int>::iterator y_i; // let's assume this iterator points to X::y
void f()
{
auto lam = [=](){
bar(y, y_i); // X::y and X::y_i are copied.
// This introduces a copy that wasn't there before,
// and means that any code that refers to y will
// now refer to the X::y of a copied object, not
// the original X::y. The copied X::y_i will still
// point to the original X::y.
};
// use lam any which way
}
};
</code>
</pre>
</p>
<p>
Yet another example:
</p>
<p>
<pre>
<code>
struct X
{
vector<int> y;
vector<int>& y2; // let's assume this reference refers to X::y
void f()
{
auto lam = [=](){
bar(y, y2); // X::y and X::y2 are copied.
// This introduces two copies that weren't there before,
// and means that any code that refers to y will
// now refer to the X::y of a copied object, not
// the original X::y. The copied X::y2 will change
// from a reference to a vector object.
};
// use lam any which way
}
};
</code>
</pre>
</p>
<a name="WhatAboutNonMemberLambdas"/><h2>Sure, but aren't such issues already present in lambdas outside classes or class member definitions?</h2>
<p>
Sure, turning references into objects and having handles with
reference/pointer-semantics refer/point to the original instead
of a copy are issues with
lambdas that appear outside classes or class member definitions. That's
a consistency argument; users need to learn two rules for a by-value
capture-default.
</p>
<p>
The author of this paper thinks the consistency argument is somewhat
questionable; outside a class or a class member definition, it's arguably
less likely that when capturing multiple entities by value, some of those
entities refer to each other in an invariant-preserving way.
The author of this paper believes it would be far more likely that
class members refer to other members in an invariant-preserving way.
Thus making it easier to copy individual members automatically increases
the risk of accidentally breaking invariants.
</p>
<p>
The second counter-argument is compatibility; we may find some amounts
of evidence that capture-defaults are rarely used inside classes or
class member definitions, or we may find style guides that ban
capture-defaults in such contexts; the problem is that there's arguably
a lot of code we can't analyze in such ways, and for some of the
presented examples, we have user reports according to which there
is existing code that relies on the current semantics.
</p>
<a name="ExplicitFullObjectCopy"/><h2>Does the proposed [*this] suffer from these problems?</h2>
<p>
The simple answer is no. It is a pure extension, so it will not
break existing code.
</p>
<a name="CopyingMembersIsHard"/><h2>"Copying members is too hard."</h2>
<p>
When the author of this paper explained the potential breakage in the
case of move-only or non-copyable/movable types, one response suggested
using init-captures. Well, right back at ya, if you want to copy
a member, an init-capture will do it; the syntax is explicit, and
there's no reliance on "default magic".
</p>
<p>
Yes, there are cases where init-captures are inconvenient, and
P0018 explains some such cases. However, P0018 also provides
a solution for most of such cases, which is explicitly copying
the whole object, aka [*this]. That solution breaks no existing code.
There are certain cases which P0018 doesn't cover, but the author
of this paper thinks it can be extended to cover those cases if need
be, again without breaking any existing code.
</p>
<a name="WhatToDo"/><h2>"Ok, hotshot, what do you recommend?"</h2>
<p>
The author of this paper has a fairly straightforward suggestion,
and that suggestion requires fairly little work: leave the semantics
of by-value capture-defaults unchanged.
</p>
<p>
The rationale for maintaining the status quo, despite there usually
being no requirement to provide any, is three-fold:
</p>
<p>
<ol>
<li>We won't introduce an incompatibility with C++11 and C++14,
we won't make all existing material on lambdas obsolete, and
we won't break code, loudly or silently.</li>
<li>We can't know how much code we would break. There are reports
according to which the amount would be non-zero.</li>
<li>To some extent, we won't trade some pitfalls for others.</li>
</ol>
</p>
</body>
</html>