-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdestructivemove.html
More file actions
2547 lines (2031 loc) · 103 KB
/
Copy pathdestructivemove.html
File metadata and controls
2547 lines (2031 loc) · 103 KB
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
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>Destructive Move, via Forwarding and Destructuring operations</title>
</head>
<body>
<h1 align="center">Destructive Move, via Forwarding and Destructuring operations</h1>
<p align="right">David Collier (dcrc2cpp@gmail.com)</p>
<h2>Contents</h2>
<ul>
<li><a href="#Introduction">1. Introduction</a></li>
<li><a href="#Summary">2. Summary</a></li>
<li><a href="#MoveableValues">3. Moveable values</a></li>
<li><a href="#ForwardingOperator">4. The forwarding operator</a></li>
<li><a href="#OwningReferences">5. Owning references</a></li>
<li><a href="#DestructiveForwarding">6. Destructive forwarding</a></li>
<li><a href="#Destructuring">7. Destructuring</a></li>
<li><a href="#LifetimeSafety">8. Lifetime-safety</a></li>
<li><a href="#LifetimeCast">9. Moving from non-automatic objects</a></li>
<li><a href="#OtherIssues">10. Other issues</a></li>
<li><a href="#Acknowledgements">Acknowledgements</a></li>
<li><a href="#References">References</a></li>
</ul>
<h2><a name="Introduction">1. Introduction</a></h2>
<p>The aim of this paper is to show how "destructive move" could
be added to C++ in a way which is compatible with the existing language.</p>
<p>This aims to be a <em>complete</em> version of destructive move —
that is, wherever we currently use a (non-destructive) move operation,
if we do not actually need to reuse the moved-from object, it should be
possible to use a destructive move instead. Indeed, merely
being <em>possible</em> is not good enough: this version of destructive
move aims to be <em>safe</em>, natural, and usable in day-to-day programming.
This contrasts with with various previous proposals which allow you to move
destructively only if you manually manage the lifetimes of the objects
involved.</p>
<h3>1.1. This is not a proposal</h3>
<p>This paper is not intended to be a proposal for standardization. Rather,
by showing what a complete proposal might look like, the hope is to provide
better context for partial proposals such as P1144 (Object relocation in
terms of move plus destroy). Certainly, if it was left up to the author of
this current paper, we would have destructive move in C++. But I also
recognize that the language changes that would be required are substantial,
and other people have different priorities. Nevertheless I hope it may be
useful to show exactly <em>how substantial</em> the changes would be,
if destructive move was implemented along these lines.</p>
<h2><a name="Summary">2. Summary</a></h2>
<p>The approach taken by this paper would require three major additions to
the language:</p>
<ol>
<li><strong>Moveable values</strong>. A moveable value is a variable whose
declaration indicates that it can be destructively moved from.
Formally a moveable value will be a kind of reference; but for most
purposes it behaves more like a non-reference variable.</li>
<li><strong>A forwarding operator,</strong> like the one proposed in P0644
(Forward without <code>forward</code>). When applied to an lvalue reference
or an rvalue reference, the operator is essentially just a shorthand for
<code>std::forward</code>; but when it is applied to a moveable value,
it performs a destructive move.</li>
<li><strong>Destructuring operations.</strong> These might look similar to
structured bindings, which we already have in C++. But what we want for
destructive move is a <em>true</em> destructuring operation: this is an
operation which ends lifetime of the complete object and provides access to
its subobjects in the form of moveable values.</li>
</ol>
<p>This paper aims to show that these three features are a powerful
combination. In particular, they combine to provide a way to write
user-defined destructive move operations:</p>
<pre><code>class C
{
T m_t;
U m_u;
public:
C(C ~ other.*) : m_t(>>other.m_t), m_u(>>other.m_u) { }
};</code></pre>
<p>Here the constructor parameter is a moveable value;
the declaration <code>other.*</code> indicates that
destructuring takes place; and the initialization of members uses the
forwarding operator <code>>></code>.</p>
<p>Some other minor language features will be required for completeness.
Additionally, although not absolutely required, this approach
works better if the order of evaluation of function arguments is defined
as being left-to-right. The reasons for this are covered in section 7.9.</p>
<h3>2.1. Goals</h3>
<p>Specific things that this paper tries to achieve:</p>
<ul>
<li><em>Relocatable</em> should be a usable concept. A class can be
Relocatable (capable of being destructively moved) without being
Move-Constructible. A function
which requires Move-Constructible in existing C++ should be
easily converted to Relocatable (assuming that conservative move
semantics are not actually desired). In particular, it should always
be possible to deal with Relocatable objects without resorting to
manual memory management.</li>
<li>It should be possible to destructively move from an automatic object,
without any danger of causing a double-destruction or non-destruction
of the object.</li>
<li>Both trivial and non-trivial destructive move operations should be
supported. That is, a type can be "trivially relocatable", in
which case its destructive move operation can be optimized to a
<code>memcpy</code>; but it should also be possible to write non-trivial user-defined destructive move operations.</li>
<li>Combinations of relocatable types should also be relocatable. For
example, if classes <code>A</code> and <code>B</code> are relocatable,
then <code>pair<A, B></code> should also be relocatable.</li>
</ul>
<p>Also one important non-goal:</p>
<ul>
<li>This paper does not attempt to achieve complete
liftetime-safety. Although the approach avoids double-destruction and
non-destruction of automatic objects, it is still possible to cause
undefined behaviour by creating dangling references.</li>
</ul>
<p>This is an attempt to do destructive move in the style of C++, not
to try and emulate other languages.</p>
<h3>2.2. Comparison with previous papers</h3>
<p>One previous approach to writing user-defined destructive move
operations is proposal P0023 (Relocator: Efficiently moving objects).
The relocation operations end up looking fairly similar to the ones
defined in this current paper. The difference is that P0023 aims directly
for the user-defined relocation: the new language features are geared
towards delivering that one operation. (The paper also has an excellent
motivation section which I won't attempt to match here.)
This current paper instead defines forwarding and destructuring operations
(which add complexity but are arguably useful in their own right)
and aims to show that user-defined relocation is a natural product of
those operations.</p>
<p>N4158 (Destructive Move) also allows user-defined relocation operations,
but this is via a customizable library function rather than adding large
new language features.</p>
<p>The recent papers P1029 ([[move_relocates]]) and P1144 (Object relocation
in terms of move plus destroy) are attempts to define a trait
<code>is_trivially_relocatable</code> while keeping the changes to
the language as minimal as possible. This seems to be a promising
direction to take, but obviously it is not an attempt at a complete
approach. Trivial relocation is an important
optimization of our existing move operations, but it does not give us
the useful new concept Relocatable.</p>
<p>Sean Parent's write-up, "Non Proposal for Destructive Move",
differs from many of the others in that it suggests the possibility of
moving destructively from automatic objects. These moves are often
implicit (if the move is the last operation to be applied to a
variable). In contrast, moves in this current paper are always explicit
(they require the use of a special operator).</p>
<h3>2.3. "Destructive Move" vs "Relocation"</h3>
<p>These terms seem to be almost interchangeable. But this paper will use
<em>relocation</em> to mean specifically constructing an object of
some type <code>T</code> by moving from (and destroying) another object
of type <code>T</code>. Whereas, <em>destructive move</em> describes
what happens to the source object during a relocation. So
"destructive move" is a more general feature which makes
relocation possible, as well as allowing other things such as creating
an object of a different type from the source.</p>
<h2><a name="MoveableValues">3. Moveable values</a></h2>
<p>If we want a <em>complete</em> implementation of destructive move, we will
certainly want to be able to move from function arguments. For example:</p>
<pre><code>std::vector<T> v;
v.push_back(makeT());
</code></pre>
<p>In current C++, this calls the rvalue reference overload of
<code>push_back</code>; the argument is then moved into the vector
using the move constructor of <code>T</code>. We would prefer this
to be a destructive move. How can we write a version of
<code>push_back</code> which does this?</p>
<p>The approach of this paper is to introduce a new kind of
function parameter. This is a <em>moveable value</em>, and is
denoted by a tilde (chosen because of the link with object
destruction — but other syntaxes are available).</p>
<pre><code>void push_back(T ~ val);</code></pre>
<p>Pass-by-moveable-value is almost identical to pass-by-value; the
key difference is whether the destruction of the function argument
takes place <em>inside</em> the function. Consider these three
function signatures:</p>
<ul>
<li><code>void f1(T && val);</code></li>
<li><code>void f2(T ~ val);</code></li>
<li><code>void f3(T val);</code></li>
</ul>
<p>If function <code>f1</code> is called with a prvalue argument, then the
temporary object that this creates is destroyed <em>by the caller</em>,
at the end of the full expression. Whereas, if function <code>f2</code>
is called, the <em>callee</em> has the responsibility for
ensuring that the object is properly destroyed. The behaviour of
<code>f3</code> depends on the implementation: for arguments passed by
value, implementations are permitted, but not required, to make
destruction the responsibility of the callee. If this behaviour <em>was</em>
required, then there would be no need for "moveable values" at
all: we could just use pass-by-value. But unfortunately there are existing
ABIs (notably the Itanium ABI) which specify that function arguments are
destroyed by the caller. It does not seem to be possible to
change the behaviour of pass-by-value without breaking
backwards-compatibility for the Itanium ABI. So moveable values are
introduced which are required to be destroyed by the callee. (In these
respects, moveable values cover similar ground to the Clang
<code>[[trivial_abi]]</code> attribute, though moveable values are declared
on an individual parameter, whereas <code>[[trivial_abi]]</code> is a class
attribute and applies to all parameters of that type.)</p>
<p>Local variables can also be declared as moveable values. Again, the
main difference between a moveable value and an ordinary non-reference
variable is that the moveable value can be destructively moved from.
It is not possible to destructively move from variables which
have not been declared as moveable.</p>
<p>Moveable values are similar to ordinary non-reference variables
in many respects:</p>
<ul>
<li>Initialization of a moveable value is similar to initialization
of a non-reference variable (see section 3.1).</li>
<li>Moveable values are <em>not polymorphic</em>. That is, the
dynamic type of the object that a moveable value refers to is always
the same as its static type.</li>
<li>The destructor of the object referred to is normally called
at the end of the scope of the moveable value. (Though this will be
modified if a destructive move takes place.)</li>
<li>No aliasing: if a moveable value or ordinary non-reference variable
refers to some object, it is not possible to simultaneously have
another moveable value in scope which refers to the same object,
or to any of its subobjects.</li>
</ul>
<p>However in some ways a moveable value acts more like a
reference:</p>
<ul>
<li>A moveable value may actually be <em>implemented</em> as
a reference (see section 3.2), and this is guaranteed for any
type which is not trivially relocatable. In particular,
initialization of a moveable value from another moveable value
is required to be a constant-time operation which is guaranteed
not to throw an exception.</li>
<li>Functions with moveable value parameters can take part in
overloading (see section 3.3).</li>
<li>Arrays can be passed by moveable value.</li>
<li>It is permitted to have a moveable value whose type is an
abstract class. (But these only exist during the process of
destroying an object of some derived class, when using destructuring
as per section 7.)</li>
</ul>
<p>In fact this paper will define a moveable value as a kind of reference,
so that <code>T~</code> is a reference type — the implications of this
are described in section 5. However the most
important properties of moveable values do not depend on this. Especially
for function parameters, it is probably best to think of moveable values
as a small modification of pass-by-value.</p>
<h3>3.1. Initialization of moveable values</h3>
<p>A moveable value of type <code>T~</code> can be initialized in one
of three ways:</p>
<ol>
<li>A moveable value can be initialized from a prvalue of type <code>T</code>.
In this case, since a moveable value is formally a kind of reference,
what happens is that the reference binds to the temporary object which
is created by evaluating the prvalue expression.</li>
<li>A moveable value can be initialized directly from an xvalue of type
<code>T~</code>. This possibility will be covered in more detail in
section 5: it implies a kind of <em>transfer of ownership</em> of the
object referred to, and this forms the basis for how destructive move
will work.</li>
<li>Otherwise, the initialization of the moveable value involves calling
a constructor or conversion operator to create a new object of type
<code>T</code>. This object is then bound to the moveable value as
in case 1. (Rarely, it is also possible to have a conversion operator
which returns <code>T~</code>. In this case transfer of ownership takes
place as in case 2.)</li>
</ol>
<p>These rules make the initialization of moveable values look very
similar to the initialization of ordinary non-reference variables.
So for example:</p>
<pre><code>auto p1 = std::make_unique<T>(x, y);
auto ~ p2 = std::make_unique<T>(x, y);
std::vector<int> v1{ 1, 3, 5 };
std::vector<int> ~ v2{ 1, 3, 5 };</code></pre>
<p>The only observable differences here are that <code>p2</code>
and <code>v2</code> can be destructively moved from,
whereas <code>p1</code> and <code>v1</code> cannot.</p>
<p>Suppose that a moveable value is initialized from an lvalue, or from
an expression of rvalue reference type. Then we are in case 3 above:
just as if we were initializing a non-reference variable, this creates a
new object by calling a constructor or conversion operator:</p>
<pre><code>void f(std::vector<int> ~ v1);
void g(const std::vector<int> & v2)
{
f(v2); // initializes the function parameter v1 with a copy of v2
}
</code></pre>
<p>That is, we cannot directly "bind" a moveable value to a
reference. In particular, calling <code>std::move</code> never results
in a destructive move:</p>
<pre><code>void f(T ~ t);
void g()
{
T t;
f(std::move(t)); // Initializes a new object by calling the usual move constructor
}
</code></pre>
<p>To enable destructive moves we will need a stronger operation than
<code>std::move</code>. This is provided by the forwarding operator
which will be described in section 4.</p>
<p>Another consequence of the above rules is that it is <em>not</em> in
general possible to initialize a moveable value <code>T~</code> with an
expression of some type derived from <code>T</code>. That is,</p>
<pre><code>class T { ... };
class U : public T { ... };
void f(T ~ t);
void g()
{
f(U{}); // Not valid in general
}
</code></pre>
<p>This would be valid if <code>T</code> has a copy constructor or
C++11 move constructor. If <code>T</code> is not move-constructible,
the author of <code>class U</code> can still arrange for <code>f(U{})</code>
to be valid, but this would imply that the object of type <code>U</code>
undergoes <em>destructuring</em> (see section 7).</p>
<h3>3.2. Moveable values can be implemented either as values or
as references</h3>
<p>This paper will require a form of move-elision when forwarding a
moveable value to a function which takes a moveable value parameter:
that operation must not call any non-trivial constructor.
This constrains the implementation of moveable values for types which are
not trivially relocatable. Effectively, it means that a moveable value
must be implemented like a reference: roughly speaking,
as a pointer to the storage of an object.</p>
<p>But for types which are trivially relocatable, it will be possible
to implement a moveable value as a value, ie. by copying the
representation of the object. If a type is small, a value
implementation is likely to be be more efficient.</p>
<p>The compiler can choose whether to use a value-like implementation or
a reference-like implementation. A fuller description of the implications
will have to wait until section 8.3: if it was done inconsistently, it
could lead to dangling references. But the intention is that moveable values
should be implemented as values for fundamental types and small structs.</p>
<p>Giving the implementation this freedom has an important consequence:
if a value implementation is permitted, then whenever a moveable value is
initialized, all existing references to the source object become invalid.
Any references to subobjects also become invalid. Use of a pre-existing
reference would be undefined behaviour — even if the
moveable value was actually implemented as a reference.
Of course, in the normal case, where the moveable value was initialized
by a temporary object, it is extremely unlikely (but not impossible) for
there to be any other references to the object.</p>
<p>The invalidation of references has a useful upside: moveable value
parameters of trivially relocatable types can be assumed not to alias
any other references.</p>
<h3>3.3. Overloading</h3>
<p>For the purposes of overload resolution, a moveable value parameter
is treated like a reference. When an argument is a prvalue, a function
with a moveable value parameter is preferred to one which takes a different
kind of reference. But when the type of an argument is an lvalue or rvalue
reference, a reference overload is preferred:</p>
<pre><code>void f(const T & t); // (1)
void f(T && t); // (2)
void f(T ~ t); // (3)
void g(const T & t); // (4)
void g(T ~ t); // (5)
void h(T && t); // (6)
void h(T ~ t); // (7)
void test(T t)
{
f(t); // calls (1)
f(std::move(t)); // calls (2)
f(T{}); // calls (3)
g(t); // calls (4)
g(std::move(t)); // calls (4)
g(T{}); // calls (5)
h(t); // calls (7) with copy-constructed argument
h(std::move(t)); // calls (6)
h(T{}); // calls (7)
}</code></pre>
<p>In all cases, these calls would be ambiguous if the
moveable value parameters were replaced with non-moveable
value parameters.</p>
<p>Note that in the case of <code>g(std::move(t))</code> it may
well turn out that calling the moveable-value overload would
have been more efficient. However, that overload will not be
selected because it would involve an additional call to the move
constructor. If optimal behaviour for rvalue references is
desired then it will be necessary to add the appropriate
overload.</p>
<h3>3.4. Order of destruction of function parameters</h3>
<p>As mentioned above, when a function has a moveable value
parameter, that function is responsible for ensuring that the object
referred to is properly destroyed. Unless a destructive move
operation takes place, this means that the destructor of the object
is invoked before the function returns. This takes place after the
destruction of any local variables.</p>
<p>If a function has more than one moveable value parameter, it
is important that the associated objects are destroyed in the
reverse order of their construction. So if function arguments
are evaluated in left-to-right order, the moveable values
should be destroyed in right-to-left order, and vice versa.</p>
<p>C++ does not currently specify the order of evaluation of
function arguments. However things would go more smoothly for
destructive move if the evaluation order was specified as
left-to-right, so that the order of destruction is right-to-left.
This then matches the situation for classes, where subobjects
are constructed in order of their declaration and are destroyed in
the reverse order. Making these two situations the same will
become important when destructuring is involved (section 7).</p>
<p>What happens if a function has a mixture of moveable value
and non-moveable value parameters? Currently implementations can
choose to make the caller responsible for the destruction of
parameters. We could keep this behaviour for non-moveable
parameters, but it would mean that those parameters would be destroyed
after all moveable value parameters, regardless of their order in
the parameter list. It seems simpler to say that, if a function has any
moveable value parameters, then destruction of other non-reference
parameters becomes the responsibility of the callee as well. So:<p>
<pre><code>void f(T ~ t1, T t2, T && t3, T ~ t4);</code></pre>
<p>Assuming that no destructive moves take place inside the function,
then, immediately before the function returns, the destructor of
<code>t4</code> is called, followed by the destructor of <code>t2</code>,
followed by the destructor of <code>t1</code>. The destruction of the
object referred to by <code>t3</code> remains the responsibility of
the caller.</p>
<h3>3.5. Functions which throw exceptions</h3>
<p>If a function parameter is a moveable value, the lifetime of the
argument will end inside the function even if the function exits via an
exception.</p>
<p>This behaviour is essential, otherwise the caller would not know whether
the argument needed to be destroyed. (It might already have been moved from
when the exception was thrown.)</p>
<p>Suppose that you are in a situation which demands a very strong form of
exception safety: you want to pass an argument to a function, and if the
function fails then the argument must not be modified. Then a moveable
value is not appropriate for this case. Perhaps you want an overload
which takes an rvalue reference instead; or perhaps you need some other
library solution (e.g. wrapping the object in a <code>std::optional</code>
in order to keep track of whether it still exists).</p>
<h3>3.6. The relocating constructor</h3>
<p>Relocation is the fundamental destructive move operation, and
moveable values provide a natural syntax for it:</p>
<pre><code>struct S
{
S(S ~ other);
};
</code></pre>
<p>We'll call this a <em>relocating constructor</em>, because
"destructive move constructor" would be a bit of a mouthful.</p>
<p>Like other special constructors, this one can be implemented by the
compiler, and the default implementation can be requested by writing
<code>=default</code>. Roughly speaking, the default implementation is to
destructively move each subobject. The precise mechanism for doing this
will need to wait until section 7.</p>
<p>A type may be <em>trivially relocatable</em>: this requires that the
default relocating constructor is used and all subobjects are also
trivially relocatable. (The C++ proposal P1144 also gives a definition
of trivial relocatability, though its definition is very different,
because it avoids expressing the concept in terms of a new
constructor. Once we do have a relocating constructor, the definition
becomes much more natural.)</p>
<p>A relocating constructor is automatically declared by the
compiler whenever a move constructor is compiler-declared.</p>
<p>If you declare a move constructor but not a relocating
constructor, then the move constructor will be chosen by overload
resolution in situations where relocation is requested. This is
completely safe and makes no difference to the result: either way the
source object is destroyed. Indeed there is not necessarily any
advantage in providing a separate relocating constructor. But there is
a benefit if the relocating constructor can be made trivial, or
if it is <code>noexcept</code> and the move constructor is not.</p>
<p>Some destructive move proposals <em>require</em> the relocation
operation to be <code>noexcept</code> if it exists. There is no real
advantage to doing this here. Even if a relocating constructor is not
declared, there is nothing to stop a user attempting to construct
a new object with a destructive move operation: this will call the move
constructor instead (or maybe even a copy constructor), which might
or might not throw exceptions. So even if you never write
a relocating constructor which throws, you might end up performing a
throwing relocation anyway. To test whether relocating a type
<code>T</code> might throw an exception, there would be a trait
<code>is_nothrow_relocatable</code>.</p>
<h3>3.7. Destructive move assignment</h3>
<p>The destructive move assignment operator is the natural companion of the
relocating constructor:</p>
<pre><code>struct S
{
S& operator= (S ~ other);
};
</code></pre>
<p>For consistency this must also be compiler-generated when appropriate.
However it is of much lesser importance than the relocating constructor:
it is unlikely to offer any advantage over regular move assignment.
In particular, if a regular move-assignment operator is not
<code>noexcept</code>, it is unlikely that a destructive move assignment
could be made <code>noexcept</code> either. For these reasons the
destructive move assignment operator will not need to be discussed further
in this paper.</p>
<h3>3.8. Template argument deduction</h3>
<p>If a function parameter is of the form <code>T~</code> where
<code>T</code> is a template parameter, then the type <code>T</code>
can be deduced, and it will always be deduced to be a
non-reference type:</p>
<pre><code>template<class T> void f(T ~ t);
std::vector<int> v;
f(5.0); // deduces T = double
f(v); // deduces T = std::vector<int>
f(std::move(v)); // also deduces T = std::vector<int>
</code></pre>
<p>This contrasts with rvalue references: there is no special
treatment for <code>T~</code> like there is for
<code>T&&</code>.</p>
<h3>3.9. Passing arrays by moveable value</h3>
<p>An array can be passed by moveable value. The syntax is the
same as for other kinds of references:</p>
<pre><code>int f(int (~arr)[3]);
void g()
{
int values[] = { 1, 3, 5 };
f(values); // initializes the parameter with a copy of the array
}
</code></pre>
<h2><a name="ForwardingOperator">4. The forwarding operator</a></h2>
<p>The forwarding operator is the key to being able to move from local
variables. Its spelling, <code>>></code>, is borrowed from P0644.
But what we want here is a <em>destructive</em> forwarding operator:
this is a more powerful operation, but at the same time,
it comes with more restrictions on where it can be used.</p>
<p>When the forwarding operator is applied to an lvalue reference
or an rvalue reference, it acts as a more convenient spelling for
<code>std::forward</code>: that is, <code>>>r</code> is
equivalent to <code>static_cast<decltype(r)></code>. This is the
original intention of the operator that was proposed in P0644.</p>
<p>However, when the forwarding operator is applied to a moveable
value, it performs a destructive move:</p>
<pre><code>void f(std::vector<T> ~ v)
{
std::vector<T> v2 = >>v; // destructive move from v into v2
}
</code></pre>
<p>The essential property of a destructive move, as opposed to a C++11
non-destructive move, is that it ends the lifetime of the source
object. In particular, there will be no destructor call for the
source object at the end of its original scope.</p>
<p>In fact we will say that the scope of the variable itself ends
when the forwarding operator is applied. This helps to prevent
use-after-move mistakes such as:</p>
<pre><code>void f(std::unique_ptr<T> p)
{
std::vector<std::unique_ptr<T>> vec;
vec.push_back(std::move(p));
std::cout << *p; // oops! Dereferences a null pointer.
}
</code></pre>
<p>With destructive move this is turned into a compile-time error:</p>
<pre><code>void f(std::unique_ptr<T> ~ p)
{
std::vector<std::unique_ptr<T>> v;
v.push_back(>>p); // scope of p ends here
std::cout << *p; // ERROR: use of p outside of its scope
}
</code></pre>
<p>P0644 gives the forwarding operator high precedence: you can forward
a function by writing <code>>>f()</code>. This paper instead gives
it the same precedence as unary <code>*</code>, which requires changing
that to <code>(>>f)()</code>.
<h3>4.1. Restrictions on the argument of the forwarding operator</h3>
<p>The argument of the forwarding operator can <em>only</em>
be the name of a local automatic variable or function parameter.
That variable must be either a moveable value or a reference.
It is not permitted to apply the forwarding operator to
ordinary non-reference variables, or to more general expressions.
For example:</p>
<pre><code>void f(T t1)
{
T t2{};
T& t3 = t2;
T~ t4 = T{};
T~ t5 = T{};
T t6 = >>t1; // ERROR: t1 not declared as moveable
T t7 = >>t2; // ERROR: t2 not declared as moveable
T& t8 = >>t3; // OK: can apply >> to a reference
T t9 = >>t4; // OK: can apply >> to moveable value
auto x = >>t4.x // ERROR: t4.x is not the name of a local reference
T t10 = >>(g()); /* ERROR: cannot apply >> to the result of a
function, even if the function returns a reference */
}
</code></pre>
<p>In particular, this means that if you want to define a local variable
that can be moved from, you must declare a moveable value, like
<code>t4</code> in the example above.</p>
<p>So if you see a non-reference variable, you know that the object
will live to the end of its scope. Whereas if you see a moveable value,
this is likely to signify that the author of the code intends to destructively
move from the object before the normal scope of the variable ends.</p>
<p>Why is it not permitted to move from non-reference local variables? We
could choose to allow it — there is no fundamental problem with that.
I have chosen to disallow it here: part of the reason is that
the declaration of a moveable value then provides a useful
visual clue that the variable might be moved from later. But also it is
for consistency with function parameters: we cannot destructively move from
function arguments passed by value, because the destruction of the object
may be the responsibility of the caller, as discussed at the start of
section 3.</p>
<h3>4.2. Scope and lifetime</h3>
<p>When the forwarding operator is applied to a moveable value,
one of two things may happen:</p>
<ul>
<li>The result of the forwarding operator may be used to initialize
a new moveable value. In this case, <em>ownership is transferred</em>:
effectively, the new moveable value becomes a new name for the same
object.</li>
<li>Otherwise, the lifetime of the object ends at the end of the
full expression.</li>
</ul>
<p>In either case, once the full effect of the expression has been
evaluated, the moveable value no longer refers to a live object.</p>
<p>This paper will define the above behaviour in terms of changes
to the C++ scoping and lifetime rules:</p>
<p><em>When the forwarding operator is applied, it ends both the
lifetime and the scope of the variable that it applies to.</em></p>
<p>Here the variable may be a moveable value, or it may be an lvalue
or rvalue reference. Either way, the lifetime of the <em>reference</em>
ends. This does not necessarily mean that the lifetime of the
referred-to object ends: if the operand is an lvalue or rvalue reference
then the lifetime of the object is unaffected by the lifetime of the
reference. And even in the case of a moveable value, the lifetime of the
object does not end immediately if ownership has been transferred.</p>
<p>Of course, if a given variable never has the
forwarding operator applied to it, then its scope and lifetime end in
the usual place, at the end of the block or function in which it
is declared.</p>
<p>Lifetimes will be dealt with more fully in section 5 —
the wording depends on whether we regard a moveable value as a non-reference
type or as a kind of reference. But here we can define the
changes to the scope of a variable:</p>
<p><em>When the forwarding operator is applied to a variable, the
scope of the variable ends at the end of the full expression.</em></p>
<p>When the scope of a moveable value is exited, the destructor of the
referred-to object will be called if and only if ownership has not been
transferred. The rules above make this condition easy for the compiler
to determine: the scope always ends in the same expression that the
forwarding operator is applied, so the compiler only ever needs to
look at the expression that has just been evaluated. This
kind of analysis is something that the compiler already needs to do,
in order to properly destroy temporary objects.</p>
<p>Suppose that a temporary object is bound to a reference, causing the
lifetime of the temporary object to be extended. Then later the
forwarding operator might be applied to the reference:</p>
<pre><code>const T & t = makeT();
f(>>t); // ends lifetime of t</code></pre>
<p>In current C++, the lifetime of the temporary object is defined to
be the same as the lifetime of the reference. If this rule continued to
apply, it would mean that the temporary object was destroyed when the
forwarding operator is applied. But this is <em>not</em> what we want here.
Applying the forwarding operator to a non-owning reference is not supposed
to be destructive. So this rule needs some modification.</p>
<p>Rather than tying the lifetime of the object to the lifetime of the
reference, we want to say that the lifetime of the object ends as if it
was declared at the same point as the reference. So its lifetime always
lasts until the end of the block.</p>
<p>Although the forwarding operator ends the scope of
variables, this does not affect name-hiding. As in existing C++,
the <em>potential scope</em> of a local variable always extends to
the end of the block in which it was declared. And similarly, the
potential scope of a function parameter extends to the end of the
function. If the forwarding operator is applied, this changes the
end of the <em>actual</em> scope, which is what determines object
lifetimes. However, if the potential scope of a name includes the
declaration of a variable with a same name, then the entire
<em>potential</em> scope of the nested variable is excluded from the
scope of the outer name:</p>
<pre><code>T x; // (1)
void f()
{
U ~ x{}; // (2)
>>x; // ends scope of the variable declared at (2)
{
V x; // (3)
std::cout << x; // OK: refers to (3)
}
std::cout << x; // ERROR: no x is in scope at this point
} // potential scope of variable (2) ends here</code></pre>
<p>In this example, on the line with the error, <code>x</code> does not
refer to the global variable declared at (1), because we are still within
the potential scope of variable (2). However it is not permitted to refer
to (2) either, because the scope of that variable has been ended
by the forwarding operator.</p>
<p>The changes to the scope and lifetime rules are probably the
single most controversial aspect of destructive move. There are other
ways to describe the changes while keeping the same behaviour: it doesn't
have to be phrased in terms of changes to the scoping rules. But any
approach eventually boils down to the same problem: there must be a way to
end the lifetime of an object before the end of the block in which it is
declared. If this is not possible, then you cannot destructively move
from objects with automatic storage duration.</p>
<h3>4.3. Scope consistency</h3>
<p>The scope of a variable is just a region of code: it does not depend
on any runtime condition. But in the previous section we said that the
forwarding operator would end the scope of the variable that it applies
to. This means that there must be restrictions on where the forwarding
operator can be used: it is not permitted to use the forwarding operator
if this would lead to any ambiguities about whether a variable was
in scope:</p>
<pre><code>T ~ t = makeT();
std::vector<T> v;
if (t.good())
{
v.push_back(>>t); // ERROR, because ...
}
use(v); // ... it would be unclear whether t was in scope at this point
</code></pre>
<p>Note that this is forbidden <em>even if <code>t</code> is never used
again after the <code>if</code> block</em>. This may sound very restrictive,
but it maintains the property that you always know whether a variable is
in scope, and hence whether the destructor of an object is yet to be called.
(We will see that there are easy ways to modify this example so that
it does compile.)</p>
<p>What if you really <em>do</em> need a variable which might or might
not refer to a valid object? Then you would use a library class such as
<code>std::optional</code> which explicitly keeps track of whether it
contains an object. But it should be rare that you need to resort to
this.</p>
<p>Note that some other versions for destructive move take a different
approach to this: these would make it legal to move from a variable
provided that it was not used later. If the compiler is unable to
<em>prove</em> whether a variable was live at a particular point in
the code, it would have to insert code to keep track of this at
runtime. But the approach of this paper is that the status of
a variable is defined in terms of its scope, which is a purely
compile-time property. That is,</p>
<p><em>The status of a reference (whether its scope and lifetime
have ended) depends only on where we are in the code. Restrictions
are placed on the use of the forwarding operator to ensure that the
status of a reference is always provably well-defined.</em></p>
<p>Note that the scope of a variable may still have multiple endpoints,
if the forwarding operator is applied in different blocks nested within
the potential scope of the variable. But these endpoints must all be
consistent with each other so that the scope is well-defined.</p>
<p>Here are the restrictions that are imposed on where the forwarding
operator can be used:</p>
<ol>
<li>The forwarding operator cannot be used inside a <code>try</code> block
if the reference it applies to was declared outside the block.</li>
<li>If the forwarding operator is used:
<ul>
<li>in an <code>if</code> or <code>else</code> block,</li>
<li>inside a loop,</li>
<li>in the cases of a <code>switch</code>,</li>
<li>in a <code>catch</code> block,</li>
</ul>
and the reference was declared outside of that scope (or in the initializer
of a <code>for</code> loop), then either:
<ul>
<li>the forwarding operator must be part of a <code>return</code> statement
or <code>throw</code> expression; or</li>
<li>there must be an unconditional <code>return</code>, <code>throw</code>,
<code>break</code> or <code>continue</code> which takes place after the
use of the forwarding operator and before the end of the block, and which
exits the potential scope of the reference.</li>
</ul>
</ol>
<p>Furthermore, as in existing C++, a <code>goto</code> statement cannot
jump into the scope of a variable unless its initialization and
destruction are trivial.</p>
<p>Examples:</p>
<pre><code>int f(std::vector<T> ~ v);
std::vector<T> makeVec(int x = 0);
int g1()
{
auto~ v = makeVec();
if (check1(v))
{
return f(>>v); // OK: >> used in return statement
}
else if (check2(v))
{
int val = f(>>v); // OK: followed by unconditional return
if (val == 0)
throw std::runtime_error("Zero");
return val;
}
else
{
int val = f(>>v); // ERROR: no unconditional return/throw
if (val != 0)
return val;
}
return -1;
}
int g2()
{
auto~ v1 = makeVec();
try
{
return f(>>v1); // ERROR: >> inside try block
}
catch (std::exception &)
{
return f(>>v1); // OK
}
try
{
auto~ v2 = makeVec();
return f(>>v2); // OK: v2 declared in same scope
}
catch (...)
{
return 0;
}
}
int g3()
{
int result = 0;
auto ~ v1 = makeVec();
for (int x = 1; x != 100; ++x)
{
auto ~ v2 = makeVec(x);
if (check(v2))
{
result = f(>>v2); // OK: break exits scope of v2
break;
}
else if (check(v1))