Topic: Destructive move, via forwarding and destructuring operations
Author: dcrc2cpp@gmail.com
Date: Sun, 20 Jan 2019 05:29:16 -0800 (PST)
Raw View
------=_Part_1335_27732159.1547990956904
Content-Type: multipart/alternative;
boundary="----=_Part_1336_124687339.1547990956905"
------=_Part_1336_124687339.1547990956905
Content-Type: text/plain; charset="UTF-8"
I've written up a version of "destructive move" for C++, attached here.
It's meant to be a complete version, so in particular:
- You can safely destructively move from automatic objects.
- It is possible to write user-defined destructive move operations.
- It supports destructive forwarding of function arguments, so that if an
argument to a forwarding function is a temporary object, then the target
function can destructively move from it.
This isn't meant to be a proposal for standardization: I expect that the
language changes are far too large for there to be any chance of seeing
something like this added to C++. But since there is continuing interest in
"relocation", and there are partial proposals for this like P1144, I
thought it might be worthwhile to write down what a complete version could
look like.
The summary is that there are three major language features involved:
- A new kind of reference, an "owning reference". Owning references are
similar to rvalue references in that they can bind to a temporary object.
But owning references can be destructively moved from, whereas rvalue
references cannot.
- A forwarding operator, like the one proposed in P0644.
- Destructuring operations. These might look similar to structured
bindings. But what we want for destructive move is a *true* destructuring
operation: this is an operation which ends the lifetime of the complete
object and returns owning references to its subobjects.
Each of these things has been suggested before in one form or another. But
I've never seen an explanation of how they combine neatly to give a full
destructive move implementation. That's what this write-up hopes to do.
Here is a user-defined destructive move implemented this way:
class C
{
T m_t;
U m_u;
public:
C(C ~ other.*) : m_t(>>other.m_t), m_u(>>other.m_u) { }
};
Here the constructor parameter type is an owning reference; the declaration
other.* indicates that destructuring takes place; and the initialization of
members uses the forwarding operator >>.
Apologies in advance if there are any inaccuracies of terminology here -
this is my first serious attempt at writing about the language. Comments
are very welcome; beware that the paper is rather long.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-proposals@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/7b2204ba-51d0-481e-9974-10029547031c%40isocpp.org.
------=_Part_1336_124687339.1547990956905
Content-Type: text/html; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable
<div dir=3D"ltr">I've written up a version of "destructive move&qu=
ot; for C++, attached here. It's meant to be a complete version, so in =
particular:<div>- You can safely destructively move from automatic objects.=
</div><div>- It is possible to write user-defined destructive move operatio=
ns.</div><div>- It supports destructive forwarding of function arguments, s=
o that if an argument to a forwarding function is a temporary object, then =
the target function can destructively move from it.</div><div><br></div><di=
v>This isn't meant to be a proposal for standardization: I expect that =
the language changes are far too large for there to be any chance of seeing=
something like this added to C++. But since there is continuing interest i=
n "relocation", and there are partial proposals for this like P11=
44, I thought it might be worthwhile to write down what a complete version =
could look like.</div><div><br></div><div>The summary is that there are thr=
ee major language features involved:</div><div><br></div><div><div>- A new =
kind of reference, an "owning reference". Owning references are s=
imilar to rvalue references in that they can bind to a temporary object. Bu=
t owning references can be destructively moved from, whereas rvalue referen=
ces cannot.</div><div>- A forwarding operator, like the one proposed in P06=
44.</div><div>- Destructuring operations. These might look similar to struc=
tured bindings. But what we want for destructive move is a *true* destructu=
ring operation: this is an operation which ends the lifetime of the complet=
e object and returns owning references to its subobjects.</div></div><div><=
br></div><div>Each of these things has been suggested before in one form or=
another. But I've never seen an explanation of how they combine neatly=
to give a full destructive move implementation. That's what this write=
-up hopes to do.</div><div><br></div><div>Here is a user-defined destructiv=
e move implemented this way:</div><div><br></div><font face=3D"courier new,=
monospace">class C<br>{<br>=C2=A0 =C2=A0 T m_t;<br>=C2=A0 =C2=A0 U m_u;<br=
> <br>public:<br>=C2=A0 =C2=A0 C(C ~ other.*) : m_t(>>other.m_t), =
m_u(>>other.m_u) { }<br>};</font><div><br><div><div>Here the construc=
tor parameter type is an owning reference; the declaration <font face=3D"co=
urier new, monospace">other.*</font> indicates that destructuring takes pla=
ce; and the initialization of members uses the forwarding operator >>=
..</div></div><div><br></div><div>Apologies in advance if there are any inac=
curacies of terminology here - this is my first serious attempt at writing =
about the language. Comments are very welcome; beware that the paper is rat=
her long.</div></div></div>
<p></p>
-- <br />
You received this message because you are subscribed to the Google Groups &=
quot;ISO C++ Standard - Future Proposals" group.<br />
To unsubscribe from this group and stop receiving emails from it, send an e=
mail to <a href=3D"mailto:std-proposals+unsubscribe@isocpp.org">std-proposa=
ls+unsubscribe@isocpp.org</a>.<br />
To post to this group, send email to <a href=3D"mailto:std-proposals@isocpp=
..org">std-proposals@isocpp.org</a>.<br />
To view this discussion on the web visit <a href=3D"https://groups.google.c=
om/a/isocpp.org/d/msgid/std-proposals/7b2204ba-51d0-481e-9974-10029547031c%=
40isocpp.org?utm_medium=3Demail&utm_source=3Dfooter">https://groups.google.=
com/a/isocpp.org/d/msgid/std-proposals/7b2204ba-51d0-481e-9974-10029547031c=
%40isocpp.org</a>.<br />
------=_Part_1336_124687339.1547990956905--
------=_Part_1335_27732159.1547990956904
Content-Type: text/html; charset=US-ASCII;
name=destructivemove-2019-01-20.html
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename=destructivemove-2019-01-20.html
X-Attachment-Id: 9afe1e4e-3821-4df1-bfb6-9bb27ff6fa63
Content-ID: <9afe1e4e-3821-4df1-bfb6-9bb27ff6fa63>
<html>
<head>
<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="#OwningReferences">3. Owning references</a></li>
<li><a href="#ForwardingOperator">4. The forwarding operator</a></li>
<li><a href="#Destructuring">5. Destructuring</a></li>
<li><a href="#LifetimeSafety">6. Lifetime-safety</a></li>
<li><a href="#LifetimeCast">7. Moving from non-automatic objects</a></li>
<li><a href="#Extensions">8. Extensions and known issues</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>A new kind of reference,</strong> which this paper calls an
<em>owning reference</em>. Owning references are similar to rvalue
references in that they can bind to a temporary object. But owning references
can be destructively moved from, whereas rvalue references cannot.</li>
<li><strong>A forwarding operator,</strong> like the one proposed in P0644
(Forward without <code>forward</code>).</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 the
lifetime of the complete object and returns owning references to its
subobjects.</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 type is an owning reference;
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 5.8.</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).</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. It should always be possible to deal with Relocatable
objects without resorting to manual memory management.</li>
<li>It should be possible to write user-defined destructive move
operations. As a particular example, if classes <code>A</code> and
<code>B</code> are Relocatable, then it should be possible to write
a relocation operation for <code>std::pair<A, B></code>.</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>The most similar paper to this one is P0023 (Relocator: Efficiently
moving objects). P0023 focuses on being able to write user-defined relocation
operations. (The paper also has an excellent motivation section which I
won't attempt to match here.) 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.
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="OwningReferences">3. Owning references</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 reference which
can be used as the function parameter here. This is an
"owning reference", 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>When an owning reference is bound to an object, the lifetime of the
object becomes managed by the reference. That is, if nothing else
happens, the destructor of the object will be called when the scope
of the reference ends. If, as above, the owning reference is a function
parameter, then that means the object will be destroyed at the end
of the function, <em>before the function returns</em>.</p>
<p>Why "if nothing else happens"? The something "else"
that might happen is a destructive move. And that's the whole point of
having an owning reference: you can destructively move from it. If a
destructive move does take place then the lifetime of the object might
end much earlier. (The mechanism for this will be explained later.)
But still, the default case, where no destructive move is requested,
is that the object's destructor is called at the end of the function.</p>
<p>Note the crucial difference between the two function signatures:</p>
<ul>
<li><code>void f(T && val);</code></li>
<li><code>void g(T ~ val);</code></li>
</ul>
<p>If function <code>f</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>g</code>
is called, the <em>callee</em> has the responsibility for
ensuring that the object is properly destroyed. If more than one temporary
object is created in a particular line of code, this may mean that the
order of destruction is very different depending on whether the objects
are passed to functions by rvalue reference or by owning reference.
Except in very unusual circumstances, objects passed by owning reference
will be destroyed inside the function that they are passed to.
(In these respects, owning references cover similar ground to the
Clang <code>[[trivial_abi]]</code> attribute.)</p>
<p>Like an rvalue reference, a <em>named</em> owning reference is actually
an lvalue. Also like an rvalue reference, an owning reference cannot be
bound to an lvalue. But there the similarities end, because an owning
reference doesn't just refuse to bind to an lvalue — it won't bind
to an rvalue reference either:</p>
<pre><code>void f(T ~ t);
void g()
{
T t;
f(std::move(t)); /* ERROR: std::move only enables conservative
move operations, not destructive moves */
}
</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>
<h3>3.1. Binding to temporary objects</h3>
<p>An owning reference can be bound to a temporary object. When this
happens, the lifetime of the temporary object becomes managed by the
reference. However, the storage of the object is <em>not</em> tied to
the reference: the storage duration is the same as it would be if
a different kind of reference was used. So:</p>
<pre><code>int f(T ~ t);
void g()
{
int x = f(T{}); // (1)
T~ t = T{}; // (2)
}</code></pre>
<p>The storage for the temporary object in line (1) can be deallocated
at the end of the full expression. Whereas in line (2), lifetime-extension
occurs so the storage for the temporary object must be preserved until
the end of the scope of <code>t</code>. Of course, in either case the usual
"as-if" rule applies, so the compiler is free to implement things
differently if it can prove that there are no live references after a
certain point.</p>
<h3>3.2. Owning references and inheritance</h3>
<p>Owning references are not polymorphic. If an owning reference has
type <code>T~</code>, the dynamic type of the object referred to is exactly
<code>T</code>. Furthermore, regardless of whether <code>T</code> has
virtual functions, it is <em>not</em> in general possible to bind an owning
reference to 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>The author of <code>class U</code> can 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 5).</p>
<p>Since an owning reference <code>T~</code> always refers to an
object of type exactly <code>T</code>, in this respect an owning reference
is similar to a non-reference variable. However, unlike for non-reference
variables, if <code>T</code> is an abstract class it is permitted to create
an owning reference <code>T~</code>.</p>
<h3>3.3. Owning references may be implemented as values</h3>
<p>In general, an owning reference would be implemented much like any other
reference: roughly speaking, it is implemented as a pointer to the storage of
an object. But in some circumstances it will be possible to implement an
owning reference as a value. If a type is small, a value implementation is
likely to be be more efficient.</p>
<p>A fuller description of when a value implementation is permitted will
have to wait until section 6.3. If it was done inconsistently, it could lead to
dangling references. But the intention is that owning references should be
implemented as values for fundamental types and small structs.</p>
<p>Giving the implementation this freedom has an important consequence:
when an owning reference is initialized, all other existing (non-owning)
references to the object may become invalid. Any references to subobjects
also become invalid. Use of a pre-existing reference would be undefined
behaviour whenever a value implementation was permitted — even if the
owning reference was not actually implemented as a value.
Of course, in the normal case, where the owning reference 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: owning reference
parameters can be assumed not to alias.</p>
<h3>3.4. Overloading</h3>
<p>Overload resolution takes account of owning references in a similar way
to our existing references. When an argument is a prvalue, a function with
an owning reference parameter is preferred to one which takes a different
kind of reference. That is, given</p>
<pre><code>void f(const T & t); // (1)
void f(T && t); // (2)
void f(T ~ t); // (3)
</code></pre>
<p>we have:</p>
<pre><code>T t;
f(t); // calls (1)
f(std::move(t)); // calls (2)
f(T{}); // calls (3)
</code></pre>
<h3>3.5. Order of destruction of function arguments</h3>
<p>As mentioned above, when a function has an owning reference
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 owning reference 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 owning references
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 5).</p>
<p>What happens if a function has a mixture of owning reference
and non-reference parameters? Currently implementations can
choose to make the caller responsible for the destruction of
non-reference parameters. We could keep this behaviour, but it
would mean that all non-reference parameters would be destroyed
after all owning reference parameters. It seems simpler to say
that, if a function has any owning reference parameters, then
destruction of 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.6. Functions which throw exceptions</h3>
<p>If a function parameter is an owning reference, 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 an owning
reference 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.7. The relocating constructor</h3>
<p>Relocation is the fundamental destructive move operation, and
owning references 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 5.</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>Note that 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 from an owning reference: 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.8. 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 be discussed further in this
paper.</p>
<h3>3.9. Reference collapsing</h3>
<p>A reference to a reference is non-owning, except in the case of an
owning reference to an owning reference. That is,</p>
<ul>
<li>If <code>X</code> is a typedef for <code>T&</code> then
<code>X~</code> is <code>T&</code>.</li>
<li>If <code>X</code> is a typedef for <code>T&&</code> then
<code>X~</code> is <code>T&&</code>.</li>
<li>If <code>X</code> is a typedef for <code>T~</code> then
<code>X&</code> is <code>T&</code> and
<code>X&&</code> is <code>T&&</code>. But
<code>X~</code> is <code>T~</code>.</li>
</ul>
<h3>3.10. Do we really need a new kind of reference?</h3>
<p>It's unfortunate that we have this extra complexity. It's particularly
unfortunate that owning references would have to coexist with our current
rvalue references — arguably owning references are "what rvalue
references should have been".</p>
<p>But for function parameters, there is no existing kind of parameter which
does the job. For destructive move to work at all, it's essential that
destruction of the argument can take place in the callee. Existing parameter
types do not allow this: if an object is passed by rvalue reference, then it
is destroyed by the caller at the end of the full expression. We could
try passing by value instead, but even in this case, implementations are
allowed to have the destruction of the argument take place in the caller.</p>
<p>So we can't move from function arguments without changing our
existing function signatures.</p>
<p>There are some possible alternatives to having a new kind of reference.
We could instead pass by value and find some other way of annotating the
parameter, or the function as a whole, to say that it is the callee's
responsibility to destroy the argument. But then we would be introducing a
similar amount of complexity anyway.</p>
<p>Once we add a new kind of reference, this solves
a number of other awkward problems. It gives us a natural signature for the
relocating constructor, for example. But probably most importantly, it gives
us a way to express the destructuring operation, which is described in a
later section.</p>
<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>To perform a destructive move, you apply the forwarding operator
to an owning reference:</p>
<pre><code>void f(std::vector<T> ~ v)
{
std::vector<T> v2 = >>v; // destructive move from v into v2
}
</code></pre>
<p>Once the forwarding operator has been applied to an owning reference,
that reference no longer manages the lifetime of the object that it
referred to. So in this example the destructor of <code>v</code> will
not be called at the end of the function.</p>
<p>Moreover, after applying the forwarding operator, the owning reference
itself becomes invalid. Any use of <code>v</code> after applying the
forwarding operator is an error. 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);
std::cout << *p; // ERROR: p not valid after >> is applied.
}
</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 variable or function parameter, whose type is
a reference. It is not permitted to apply the forwarding operator
to other expressions, or to non-reference variables. For example:</p>
<pre><code>void f(T t1)
{
T t2{};
T~ t3 = T{};
T~ t4 = T{};
T t5 = >>t1; // ERROR: t1 is not a reference
T t6 = >>t2; // ERROR: t2 is not a reference
T t7 = >>t3; // OK
auto x = >>t4.x // ERROR: t4.x is not the name of a local reference
T t8 = >>(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 an owning reference: this can
then bind to the actual object which is to be moved from, like
<code>t3</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 an owning reference,
this is likely to signify that the author of the code intends to destructively
move from the object before the scope of the reference 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 presence of an owning reference 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 in section 3.8.</p>
<h3>4.2. Tracking which references are live</h3>
<p>As stated above, if the forwarding operator is applied to an owning
reference, then the destructor will no longer be called when the scope
of the reference ends. We will say that the reference is <em>live</em>
if the forwarding operator has not yet been applied to it; after
the operator is applied it is <em>dead</em>. If an owning reference is
still live at the end of its scope, then the destructor of the object
is invoked. So clearly it is important that the compiler must be able
to keep track of which references are live.</p>
<p>Proposals for destructive move seem to have two possible approaches
available for this. One is that, if the compiler is unable to
<em>prove</em> whether a reference is live at a certain point in the
code, it must insert code to keep track of this at runtime.</p>
<p>This paper uses the alternative approach:<p>
<p><em>Whenever a reference is in scope, the status of the reference
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>So code like this will not be permitted:</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 live 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 draconian, but
it maintains the property that you always know whether a reference is live,
and 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>Here are the restrictions that are imposed to ensure that the status
of a reference is always well-defined:</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 scope of the reference.</li>
</ul>
<li>A <code>goto</code> statement cannot jump from a part of the code where
a reference is dead to a part where the reference is live.</li>
</ol>
<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))
{
result = f(>>v1); // ERROR: break does not exit scope of v1
break;
}
result += f(>>v1); // ERROR: v1 declared outside of loop
result += f(>>v2); // OK
}
return result;
}</code></pre>
<h3>4.3. Destructive moves in branches of an <code>if</code></h3>
<p>It is quite common to want to destructively move from an object in
both branches of an <code>if</code>. For example:</p>
<pre><code>std::vector<T> good, bad;
for (int i = 0; i != 100; ++i)
{
T~ t = makeT(i);
std::cerr << "Considering " << t << "\n";
if (t.is_good())
{
good.push_back(>>t);
}
else
{
bad.push_back(>>t);
}
}
</code></pre>
<p>According to the rules of section 4.2, this is not valid. We could loosen
the rules to make it valid — after all, regardless of which branch is
taken, <code>t</code> will always have been moved from afterwards. This
would be consistent with the general principle that it should be well-defined
whether a reference is valid.</p>
<p>But this paper does not suggest loosening the restrictions in this way.
Instead, you can <em>explicitly</em> tell the compiler that both branches
of an <code>if</code> will destroy the reference, by using an <code>if</code>
statement with initializer (from C++17). That is, you could change the
example to:</p>
<pre><code>std::vector<T> good, bad;
for (int i = 0; i != 100; ++i)
{
T~ t = makeT(i);
std::cerr << "Considering " << t << "\n";
if (T~ tmp = >>t; tmp.is_good())
{
good.push_back(>>tmp);
}
else
{
bad.push_back(>>tmp);
}
}
</code></pre>
<p>If an <code>if</code>-with-initializer is used like this, but the
forwarding operator is only applied in one of the branches of the
<code>if</code>, the object will still be destroyed when the other branch
is taken (after the code for that branch has executed).</p>
<p>Using an <code>if</code>-with-initializer means that we are being very
explicit about what happens to the object; but it is also quite verbose
and requires creating a second name for the same reference. So perhaps it
would be better to have some syntactic sugar along these lines:</p>
<pre><code>std::vector<T> good, bad;
for (int i = 0; i != 100; ++i)
{
T~ t = makeT(i);
std::cerr << "Considering " << t << "\n";
if >>t (t.is_good())
{
good.push_back(>>t);
}
else
{
bad.push_back(>>t);
}
}
</code></pre>
<p>But much of the time this is unnecessary anyway because we might not
need to use the object before the <code>if</code> statement at all. For
example we can change the original example from section 4.2 like this to
make it compile:</p>
<pre><code>std::vector<T> v;
if (T~ t = makeT(); t.good())
{
v.push_back(>>t); // OK
}
use(v);
</code></pre>
<h3>4.4. The effect of the forwarding operator</h3>
<p>If <code>r</code> is the name of an owning reference then
<code>decltype(>>r)</code> is also an owning reference. That is,
the expression <code>>>r</code> gives us an <em>unnamed</em>
owning reference, in the same way that <code>std::move</code>
returns an unnamed rvalue reference. (Sadly this means that yet another
value category is needed: the owning reference equivalent
of an xvalue.)</p>
<p>Often, the result of the forwarding operator will be used to initialize
a named owning reference. Naturally, in this case, the named
owning reference becomes the owner of the object. But we also need to
define what happens if <code>>>r</code> is <em>not</em> used to
initialize an owning reference. Suppose that <code>>>r</code> appears
in a complicated expression such as</p>
<pre><code>auto res = f1(f2(), >>r, f3()) + f4();</code></pre>
<p>When does the lifetime of the object end in this example? The way this
is defined is to imagine replacing <code>>>r</code> with a prvalue
expression of the same type as the object that <code>r</code> refers to.
The lifetime of the object referred to by <code>r</code> ends at
the same time as the hypothetical temporary object created by the equivalent
prvalue expression.</p>
<p>So, first suppose that <code>f1</code> takes non-owning reference
parameters, and assume that no exceptions are thrown during the
evaluation of the expression. Then the lifetime of the object ends
at the end of the full expression, between the destruction of the
objects returned by <code>f3</code> and <code>f2</code>.</p>
<p>If instead <code>f1</code> takes owning reference parameters,
the lifetime of the object becomes managed by the parameter,
and will end inside the function.</p>
<p>If <code>f1</code> takes its parameters by value, then everything
depends on which constructor is called to create the value. If a relocating
constructor is found then the object will be destroyed as part of creating
the value; otherwise things will be like the non-owning reference case.</p>
<p>What if an exception is thrown by <code>f3</code>? Then (assuming
left-to-right evaluation) the destructor of the object <code>r</code>
refers to will be invoked, before the destructor of the object
returned by <code>f2</code>. Note that this happens before the
destruction of other local variables, even if <code>r</code> was
declared before them.</p>
<p>Finally, suppose that an exception is thrown by <code>f2</code>. In
that case, the subexpression <code>>>r</code> will not have been
evaluated: the corresponding hypothetical temporary object that we are
considering would never have been constructed. In this case, the object
<code>r</code> refers to is destroyed along with other local variables, in
its usual place. (So it is destroyed after any local variables which were
declared after it.) Note that according to the rules in section 4.2,
the exception cannot be caught inside the scope of <code>r</code>,
so the object will always be destroyed during the unwinding for
this exception.</p>
<h3>4.5. Forwarding with the forwarding operator</h3>
<p>In all of the examples up to now, the forwarding operator has only ever
been applied to owning references. But the forwarding operator can also
be applied to other (lvalue or rvalue) references. In each case, the result
type of the forwarding operator is the same as the declared type of its
argument. This is what gives the operator its name: it can be used as a
replacement for <code>std::forward</code>, as originally intended in
P0644:</p>
<pre><code>template<typename Func, typename ... Args>
auto invoke(Func && f, Args && ...args)
{
return (>>f)(>>args ...);
}
</code></pre>
<p>Applying the operator to an lvalue reference or rvalue reference is not
destructive: it does not change the lifetime of the object referred
to. However it still makes the <em>reference</em> itself invalid:
it is a compile-time error to use the reference after the forwarding
operator has been applied. The restrictions in section 4.2 also
still apply.</p>
<p>So if <code>invoke</code> is defined as above it will <em>only</em> do
non-destructive forwarding. Even if we pass an owning reference expression
such as <code>>>r</code> as an argument, that parameter will still
be deduced to be a (non-owning) rvalue reference.</p>
<p>But it is also possible to do <em>destructive forwarding</em>. The
mechanism for this is a direct copy of the existing C++ forwarding
mechanism. (Arguably the existing mechanism is flawed and confusing,
and perhaps a different syntax should have been chosen. But this paper
does not attempt to fix that.)</p>
<p>In the existing forwarding mechanism, given a template parameter
<code>T</code>, a function parameter declared as <code>T&&</code>
is a forwarding reference. Similarly, a function parameter declared as
<code>T~</code> will be a <em>destructive forwarding reference</em>. The
reference-collapsing rules in section 3.9 are designed to make this work:</p>
<pre><code>template<typename Func, typename ... Args>
auto invoke(Func ~ f, Args ~ ...args)
{
return (>>f)(>>args ...);
}
</code></pre>
<p>With this definition:</p>
<ul>
<li>If an argument is a prvalue expression of type <code>T</code>, then the
corresponding template parameter will be deduced as <code>T</code>, and the
function parameter will have type <code>T~</code>.</li>
<li>If an argument is an owning reference <code>T~</code>, then the
corresponding template parameter will again be deduced as <code>T</code>, and
the function parameter will have type <code>T~</code>.</li>
<li>If an argument is an lvalue <code>T&</code>, then the
corresponding template parameter will be deduced as <code>T&</code>,
and the function parameter will have type <code>T&</code>.</li>
<li>If an argument is an rvalue reference <code>T&&</code>, then the
corresponding template parameter will be deduced as <code>T&&</code>,
and the function parameter will have type <code>T&&</code>.</li>
</ul>
<p>So if we pass a temporary object or owning reference as an argument,
it will be destructively forwarded (forwarded as an owning reference).
Whereas if we pass an lvalue or rvalue reference, it will be forwarded as-is.
This is precisely what we would want for destructive forwarding: it allows
<code>f</code> to destructively move from temporary objects but not
from lvalues, just like if <code>f</code> was called directly.</p>
<h3>4.6. Destructive move of <code>*this</code></h3>
<p>A class member function can have an owning ref-qualifier,
in which case it is possible to move from <code>*this</code>
by applying the forwarding operator:</p>
<pre><code>struct S
{
T convert_to_t() ~
{
return convert_s_to_t(>>*this);
}
};</code></pre>
<p>This is a special exception to the principle that the
forwarding operator can only be applied to the name of a reference.</p>
<p>A member function with an owning ref-qualifier cannot be virtual,
because owning references are not polymorphic. (The possibility of
polymorphic owning references is discussed in section 8.2.)</p>
<h2><a name="Destructuring">5. Destructuring</a></h2>
<p>In order to be able to write user-defined destructive move operations,
there must be a way to end the lifetime of an object without calling its
destructor. This is provided by <em>destructuring</em> operations.</p>
<p>A destructuring operation ends the lifetime of an object by
"splitting it" into its subobjects. It does this by
taking an owning reference to the original object and returning
owning references to each of the subobjects.
Because owning references are returned to each subobject, the
destructuring operation ensures that each subobject is properly
destroyed. But the destructor of the original object is not
called.</p>
<p>The destructuring operation for a class is normally <em>private</em>.
Accessibility is discussed in section 5.4.</p>
<p>The most natural syntax for destructuring is an extension of
C++17 structured bindings. This is suggested in section 5.7.
However there are some issues with this: it requires flattening
the subobjects into a single list, which may be awkward if different
subobjects have different accessibility, or some subobjects are generated
from pack expansion. So while the interaction with structured bindings
is important, the basic syntax for destructuring actually looks a
little different.</p>
<h3>5.1. Destructured owning reference declarations</h3>
<p>When an owning reference is declared, its declaration
can indicate that destructuring should happen after the reference is
initialized.</p>
<p>The chosen syntax is to add <code>.*</code> to the name of the
owning reference:</p>
<pre><code>struct Pair { T t; U u; };
void f()
{
Pair ~ p.* = Pair{ /* ... */ };
}</code></pre>
<p>When this line of code is executed, first the owning reference
<code>p</code> is initialized from the <code>Pair</code> object as normal.
This includes the destruction of any temporary objects as usual.
After this, a destructuring takes place: the lifetime of the <code>Pair</code>
ends, and owning references are created to the subobjects. These
owning references are given the names <code>p.t</code> and
<code>p.u</code>. (This explains the choice of syntax: <code>p.*</code>
is suggestive of a wildcard expression.)</p>
<p>Note that although <code>p.t</code> and <code>p.u</code> look like
member accesses, in fact they are treated as <em>names</em> of
owning references. It is not valid to refer to <code>p</code> itself,
except as part of the names <code>p.t</code> and <code>p.u</code>.
Since <code>p.t</code> and <code>p.u</code> are names of owning
references, the forwarding operator can be applied to them.
(Per section 4.1, if <code>p</code> is not a destructured
reference, an expression like <code>>>p.t</code> is invalid. It only
becomes valid when the declaration is changed to <code>p.*</code>)</p>
<p>We can have destructured function parameters:</p>
<pre><code>void f(Pair ~ p.*)
{
U~ u = >>p.u;
}
</code></pre>
<p>Note that this does not affect the signature of the function.
A function can be declared with normal owning reference
parameters, and later defined with destructured parameters.
The destructuring takes place at the start of execution of the function:
if an exception is thrown while function arguments are being
evaluated, then any owning reference parameters which have already
been initialized are not destructured (their normal destructors will
be called). Note that the destructuring operation itself can
never throw an exception.</p>
<p>If we do not destructively move from <code>p.t</code> or <code>p.u</code>,
then they will be destroyed in the reverse order of their declaration
in the class. So destructuring an object does not
change the order of destruction of its subobjects, unless they are
explicitly moved from.</p>
<h3>5.2. Classes with reference members</h3>
<p>If a class member is a reference, then the reference collapsing rules of
section 3.8 apply. So if we have:
<pre><code>struct Pair2 { T & tref; U && uref; };
void f(Pair2 ~ p.*)</code></pre>
<p>then in the body of <code>f</code>, <code>p.tref</code> and
<code>p.uref</code> are non-owning references: <code>decltype(tref)</code> is
<code>T&</code> and <code>decltype(uref)</code> is
<code>U&&</code>.</p>
<h3>5.3. Base class subobjects</h3>
<p>If a class has base classes, destructuring creates owning references to the
base class subobjects as well as the member subobjects.</p>
<p>Since owning references are not polymorphic, this means that the dynamic
type of a base class reference changes during the destructuring operation.
Note that this mirrors what happens when the normal destructor is called:
after the body of a destructor has executed, a kind of destructuring takes
place.</p>
<p>The syntax for destructured owning references needs some extension
in order to work with base classes. In current C++, the way to obtain a
reference to a base class is to use
<code>static_cast<Base&>(o)</code>.
This doesn't really work for our purposes. So another minor language feature
is needed: a way to refer to base class subobjects which provides uniformity
with the existing syntax for member subobjects. The suggested syntax is
<pre><code>o.class<Base></code></pre>
<p>Arguably this is a useful feature in its own right: it provides a
simple syntax for upcasting (which is always safe), distinguishing it
from downcasting (which may fail). But the main point is that if
<code>o</code> is the name of a destructured owning reference, then
the forwarding operator can be applied as
<code>>>o.class<Base></code>.</p>
<h3>5.4. Accessibility</h3>
<p>A class has a <em>public</em> destructuring operation if two conditions
hold:</p>
<ol>
<li>All of the members and bases of the class must be public.</li>
<li>The class must not have a user-provided destructor.</li>
</ol>
<p>If a class has any private members or bases, or if it has a user-provided
destructor, then the destructuring operation is private.</p>
<p>We can now complete the picture of section 3.1 by explaining what happens
when trying to initialize an owning reference to a base class with
a temporary object of some derived class:</p>
<pre><code>class T { ... };
class U : public T { ... };
void f(T ~ t);
void g()
{
f(U{}); // Valid only if the destructuring operation for U is public
}
</code></pre>
<p>If <code>U</code> has a public destructuring operation then this code
is valid, and an implicit destructuring takes place when the function
argument is evaluated.</p>
<p>Suppose that we change the example so that <code>T</code> is a base
of <code>U</code> but not a direct base. Then additionally all intermediate
base classes must be publicly destructurable.</p>
<p>It is not sufficient for the destructuring operation to be
<em>accessible</em>. If a class has a user-provided destructor then
a derived-to-base conversion of owning references is invalid even
when the destructuring operation is accessible. Otherwise it would be
too easy to accidentally destructure a class which performed important
logic in its destructor.</p>
<p>However, the class author can provide an explicit conversion
if desired:</p>
<pre><code>class U : public T
{
public:
operator T~ () ~;
};</code></pre>
<p>This makes the derived-to-base conversion permitted even if the
default destructuring operation would not be usable. And this
explicit conversion will be used to perform the derived-to-base
conversion whenever it is requested, even if the default destructuring
operation would have been usable.</p>
<h3>5.5. Implementation of the relocating constructor</h3>
<p>This is where all of the features described so far finally
come together. A relocating constructor can be implemented like
this:</p>
<code><pre>class C : public B
{
T m_t;
U m_u;
public:
C(C~ other.*) :
B(>>other.class<B>),
m_t(>>other.m_t),
m_u(>>other.m_u) { }
};</pre></code>
<p>That is, the source object is destructured, then each base and member
is destructively moved in turn. This is the default implementation: it is
what the compiler will generate if requested by <code>=default</code>.
Of course, if the default implementation is not suitable, it is possible
to write the relocating constructor differently.</p>
<h3>5.6. Lambdas and destructuring of <code>*this</code></h3>
<p>In a member function which is ref-qualified by owning reference, it is
easy enough to destructure <code>*this</code> by writing</p>
<pre><code>auto~ self.* = >>*this;</code></pre>
<p>But for consistency with other function parameters, it would be
preferable to have syntax to make <code>*this</code> a destructured
function parameter as per section 5.1. This could be achieved by putting
<code>.*</code> after the ref-qualifier. Then the subobjects can be accessed
either by their name or by <code>this->name</code>. For example we can
implement an explicit derived-to-base conversion like this:</p>
<pre><code>class C : public B
{
public:
operator B~ () ~.*
{
return >>this->class<B>;
}
};
</code></pre>
<p>It ought to be possible to create a lambda whose function call
operator is owning-ref-qualified. These represent functions which can
only be called once. These lambdas are <em>automatically destructured</em>
when they are invoked — so if a lambda captures an object by copy,
you have an owning reference to this object in the the body of
the lambda.</p>
<p>The suggested syntax for a single-use lambda is to place a tilde
after the parameter list (if present). It is not necessary to write
<code>.*</code> because the destructuring happens automatically:</p>
<pre><code>auto~ p = std::make_unique<T>();
std::thread thr([p = >>p]() ~ { pass_to_sink(>>p); });
</code></pre>
<p>(A further extension would be to allow lambda capture by
owning reference.)</p>
<h3>5.7. Structured bindings and custom destructuring</h3>
<p>It is not absolutely necessary to have more than one syntax for
destructuring. If you are the author of a class, destructured owning
references will do everything that you need. However for <em>users</em>
of a class, other forms of destructuring would be more appropriate.</p>
<p>C++ already has structured bindings, which provide
similar-looking functionality to the destructuring operation
that we want. We can extend that syntax to perform a
true destructuring:</p>
<pre><code>struct Pair { T t; U u; };
void f(Pair ~ p)
{
auto~ [t, u] = >>p;
}</code></pre>
<p>It's important to note that C++17 structured bindings are <em>not</em>
destructuring operations: the original object is still there, and the
bindings are just aliases for parts of that object.
But when the owning reference qualifier is used as above, it becomes a true
destructuring: the lifetime of the original object ends and new owning
references <code>t</code> and <code>u</code> are initialized which
refer to the subobjects of the original <code>Pair</code>.</p>
<p>The structured bindings syntax can be overloaded in this way because
<code>auto~ [t, u]</code> would not otherwise have any useful meaning.
It would be pointless for <code>t</code> and <code>u</code> here to be
non-owning references: if you wanted that, you could write
<code>auto&& [t, u]</code>. So when the owning reference
qualifier is used, <code>t</code> and <code>u</code> are defined to
be owning references — and by definition, this means that a
destructuring operation must take place.</p>
<p>Because <code>t</code> and <code>u</code> are named owning references,
the forwarding operator can be applied to them. So we can destructively
move from the subobjects of our original object.</p>
<p>By default, these structured bindings can only be created if the
destructuring operation is accessible. However, for consistency with
C++17 structured bindings, it should be possible to provide customized
destructuring for other tuple-like types. Unfortunately the existing
customization points for structured bindings (<code>std::get</code> etc.)
do not work when true destructuring is involved. It is not possible to
write a function which returns an owning reference to one subobject without
also destroying the other subobjects. What you <em>can</em> do is provide
a customized conversion of the entire original object to some other type
which is publicly destructurable. This could be expressed in terms of
operator overloading, something like:</p>
<pre><code>class MyPair
{
T m_t;
U m_u;
public:
std::pair<T~, U~> operator [.*]() ~
{
auto~ self = >>*this;
return { >>self.m_t, >>self.m_u };
}
};
void f()
{
auto~ [t, u] = MyPair{};
}
</code></pre>
<p>Analogously to <code>operator -></code>,
this <code>operator [.*]</code> must either return a publicly
destructurable type, or return another type which has an
overoaded <code>operator [.*]</code>. Note that <code>std::tuple</code>
itself would need to be made destructurable in this way.</p>
<p>An object which is destructured using <code>operator [.*]</code> would
have its storage duration extended to encompass that of the
returned object.</p>
<h3>5.8. Destructuring by unpacking</h3>
<p>There is one more syntax for destructuring that may be useful, which is
taken from proposal P0535 (Generalized unpacking and parameter pack slicing).
This might be considered an optional extra, but it fits in nicely with
the other operations in P0535.</p>
<p>P0535 specifies that if <code>t</code> is a product type then
<code>[:]t</code> should expand to a parameter pack equivalent to</p>
<pre><code>get<0>(t), get<1>(t), ...</code></pre>
<p>If <code>t</code> is replaced by an expression which is a prvalue or
an owning reference, we can modify this to expand to the set of
references returned by the destructuring operation. This allows the
subobjects to be forwarded to another function:</p>
<pre><code>struct Pair { T t; U u; };
void f(T ~ t, U ~ u);
void g()
{
f([:]Pair{}...);
}</code></pre>
<p>Note that the exact behaviour here depends on
the order of evaluation of function arguments.
If function arguments are evaluated left-to-right, then they will be
destroyed in right-to-left order, which is the same as their usual order of
destruction when a <code>Pair</code> is destroyed. However, if function
arguments are evaluated right-to-left, then the order of destruction will
be changed by forwarding the subobjects. In principle, this shouldn't matter:
if it is crucial to maintain the order of destruction of class members, then
the class should not be publicly destructurable in the first place. But
it would still be inconsistent with the rest of C++ if forwarding subobjects
were to change their order of destruction. This is why destructive move
works best if the order of evaluation of function arguments is
defined as left-to-right.</p>
<h3>5.9. Destructuring of arrays</h3>
<p>Arrays can be destructured using the syntaxes of 5.7 or 5.8, e.g.</p>
<pre><code>void f(T (~arr)[3])
{
auto~ [t0, t1, t2] = >>arr;
}</code></pre>
<p>For consistency, it should also be possible to create a destructured
owning reference to an array, as in section 5.1:</p>
<pre><code>void f(T (~arr.*)[3])
{
auto~ t = >>T[0];
}</code></pre>
<p>In this function, <code>T[0]</code>, <code>T[1]</code> and
<code>T[2]</code> are treated as being the names of owning references,
to which the forwarding operator can be applied. As ever, the compiler keeps
track of their lifetimes as if they were separate variables. But there is
an important restriction that comes with this: the index
must always be a constant expression. So it is not possible to use
a normal <code>for</code> loop to iterate over the array. In
practice this means that manipulation of the destructured array elements is
likely to be via an <code>index_sequence</code>.</p>
<p>For most purposes, a more convenient alternative would be to use a
library class like the <code>owning_span</code> defined in section 7.1.</p>
<h2><a name="LifetimeSafety">6. Lifetime-safety</a></h2>
<p>As mentioned in 2.1, it is not a goal of this paper to provide complete
lifetime-safety. As in existing C++, it is possible to create dangling
references. But it is still important to be clear on exactly what is safe
and what is not.</p>
<h3>6.1. Dangling references</h3>
<p>It is possible to obtain a non-owning reference which refers to the same
object as an owning reference. If the forwarding operator is applied to the
owning reference, then the non-owning reference should be assumed to be
dangling:</p>
<pre><code>void f()
{
auto~ v = std::vector<T>{ /* ... */ };
auto& vref = v1;
pass_to_sink(>>v);
use(vref); // Undefined behaviour: vref is a dangling refrence
}</code></pre>
<p>The compiler may be able to diagnose this as an error but is not required
to do so. This is in keeping with how other dangling references are treated
in C++.</p>
<p>But this cannot be the whole story, because some classes contain
self-references. If those self-references were invalidated when an owning
reference was contructed, it would not be possible to implement
destructive move operations for the class. So the actual rules are
more subtle:</p>
<ol>
<li>When the lifetime of an object ends (either via a destructuring
operation of the usual destructor), all references to that object
become invalid.</li>
<li>Additionally, whenever an owning reference is initialized which
refers to a trivially relocatable object, all existing
references to that object and its subobjects become invalid.</li>
</ol>
<p>Note that a destructuring operation creates owning references
to all subobjects, and so point 2 applies to those subobjects
if they are trivially relocatable. This means that, even if
a class is not trivially relocatable, references to
subobjects may be invalidated as soon as destructuring occurs.</p>
<p>The reason for point 2 is that the compiler may implement
an owning reference to a trivially-reclocatable type as a value.
However, the invalidation of references occurs whether or not it
is implemented that way.</p>
<p>Clearly, applying these rules requires special knowledge of the
class. Also if an owning reference is passed to a function, the lifetime
of the object depends on the implementation of the function. So except
in very special circumstances where you do control both of these things,
the only safe approach is to assume that a reference is dangling once
ownership has been transferred.</p>
<h3>6.2. Owning references to deallocated storage</h3>
<p>Another kind of dangling reference occurs when a live owning reference
exists after the storage for the referred-to object has been deallocated.
Normally scoping rules prevent this from happening, but it is possible if
an owning reference is stored as a class member or returned from a
function:</p>
<pre><code>T~ dangerous(T~ t) { return >>t; }
void f()
{
T~ owner = dangerous(T{}); // Undefined behaviour
}
</code></pre>
<p>Here the storage for the temporary <code>T{}</code> may be deallocated at
the end of the full expression, so the owning reference <code>owner</code> is
still live after the storage is deallocated. This is undefined behaviour,
because even if the reference is never explicitly used again in the function,
it will still be used to call the object's destructor at the end of
its scope.</p>
<p>Note that returning an owning reference from a function should be very
rare, much like returning an rvalue reference is in current C++. The only
common cases should be the implementation of custom destructuring operations
(sections 5.4 and 5.7) for which there are special rules ensuring that the
object storage duration is extended.</p>
<h3>6.3. Implementation of owning references</h3>
<p>The compiler can implement an owning reference <code>T~</code> as
a value provided that <code>T</code> is trivially relocatable.</p>
<p>However, the compiler must be able to prove that this is safe.
That is, if the code is valid (no undefined behaviour
according to the rules of 6.1 and 6.2), implementing the
owning reference as a value must not lead to any new dangling
references.</p>
<p>This is an important example:</p>
<pre><code>struct Pair { T t; U u; };
T ~ destructure_to_t(Pair ~ p.*)
{
return >>p.t;
}
void f()
{
Pair ~ p1 = Pair{};
T ~ t = destructure_to_t(>>p1);
}</code></pre>
<p>This code is valid. The function <code>destructure_to_t</code> is dangerous,
and would lead to undefined behaviour if called with a temporary argument,
but this particular use of it is OK. The original storage for the
<code>Pair{}</code> object is not deallocated until the end of the scope
of <code>p1</code>, so it is valid for <code>t</code> to refer to a subobject
in that storage, even after the original object has been destructured.</p>
<p>So the implementation of owning references must not break this
example.</p>
<p>In particular, consider the <code>Pair~</code> parameter of the function
<code>destructure_to_t</code>. If this is implemented as a reference there is
no problem: it refers to the original storage for the <code>Pair</code>. But
if <code>Pair~</code> is to be implemented as a value, the returned
<code>T~</code> cannot refer to the new storage allocated for the value.
So to make this work, returned <code>T~</code> must also be implemented as
a value.</p>
<p>More generally, it is safe to implement some owning references as values
if two rules are followed:</p>
<ul>
<li>Whether an owning reference is implemented as a value should be a
consistent property of the type. That is, if some owning references for
a particular type are implemented as values, then all of them should be.</li>
<li>If a type has owning references implemented as values then all
of its subobject types must also have their owning references implemented
as values.</li>
</ul>
<p>It may be possible to be slightly less restrictive, but then the compiler
would need to be able to prove that the implementation is safe.</p>
<p>There is one situation where owning references <em>must</em> be implemented
as values: bitfields. Support for bitfields is necessary if classes which
contain them are to be destructured. But it is not possible to have a plain
reference to a bitfield: it must be copied. If we follow the two rules above
then this implies that <em>all</em> owning references to integers must be
implemented as values. Fortunately, even if we were not forced down this path,
it would be desirable to implement <code>int~</code> as a value anyway.</p>
<h2><a name="LifetimeCast">7. Moving from non-automatic objects</a></h2>
<p>In every example up to this point, owning references have been initialized
either from other owning references, or by binding to temporary objects.
We also need to be able to create owning references in other situations,
e.g. from an object created by placement new.</p>
<h3>7.1. Casts which change ownership</h3>
<p>In order to support manual memory management, there needs to be a way to
obtain an owning reference from a non-owning reference.</p>
<p>The corresponding approach for rvalue references is to call
<code>std::move</code>, which is implemented as
<code>static_cast<T&&></code>. But this paper does not suggest
allowing <code>static_cast</code> to create an owning reference: it would
be surprising and potentially dangerous if <code>static_cast</code> could
do something as powerful as changing object lifetimes.
The suggestion instead is to have a new kind of cast,
<code>lifetime_cast</code>, which specializes in changing ownership. (This
is analogous to the way that <code>const_cast</code> specializes in
changing constness.)</p>
<p>Because owning references are not polymorphic, the expression
<code>lifetime_cast<T~>(r)</code> leads to undefined behaviour if
<code>r</code> refers to an object whose dynamic type is not
exactly <code>T</code>.</p>
<p>To illustrate the use of <code>lifetime_cast</code> we can implement the
function <code>uninitialized_destructive_move_n</code> from N4158, which
relocates an array of objects from one buffer to another:</p>
<pre><code>void uninitialized_destructive_move_n(T* from, size_t sz, T* to)
{
for (size_t i = 0; i != sz; ++i)
{
new(to + i) T(lifetime_cast<T~>(*(from + i)));
}
}</code></pre>
<p>(This assumes that the relocation operation cannot throw: more care would
be need to taken otherwise.)</p>
<p>Note that the above code properly ends the lifetime of the source objects,
and begins the lifetime of objects in the target buffer. There is no need for
any magic to allow the lifetimes of objects to begin and end without
contructor or destructor calls, which might be necessary in a partial
destructive move proposal.</p>
<p>The reverse operation, casting away ownership, is not required as often.
But it can be used to create classes which manage the lifetime of other
objects. For example, an <code>owning_span</code> is a class which manages
the lifetime of a contiguous array of objects. It can be constructed from
an owning reference to an array:</p>
<pre><code>template<typename T> class owning_span
{
T* m_data;
size_t m_sz;
public:
template<size_t Size> owning_span(T (~a)[Size]) :
m_data(lifetime_cast<T(&)[Size]>(>>a)),
m_sz(Size) { }
/* (Destructor and other member functions not shown) */
};</code></pre>
<p>Without the <code>lifetime_cast</code> here, the array would be destroyed
inside the constructor. Casting away the ownership allows the class to
manually manage ownership through the <code>m_data</code> pointer. This will
be valid provided that the storage for the array outlives the
<code>owning_span</code> object.</p>
<h3>7.2. Moving objects out of containers</h3>
<p>In existing C++, moving an object out of a container is a two-step process:
first you use the element's move constructor to initialize a new object, then
separately the moved-from object is erased from the container. This two-step
process cannot work with destructive move: you cannot have destructed objects
in the container, even temporarily. So the two steps need to be combined into
a single operation.</p>
<p>So in order to move objects out of containers, we would need new
container member functions. This is a little unfortunate, because preferred
C++ style is to keep class interfaces as minimal as possible. But one of the
main jobs of a container is to manage the lifetime of its objects. So it
is completely appropriate that the function which moves objects out of a
container should be implemented by the container.</p>
<p>Moving a single element out of a container is easy: the element can be
returned by value. Indeed, the fact that <code>pop_back</code> and similar
functions do <em>not</em> return the popped element is often considered
a bit surprising. The existing functions probably cannot be changed, but
there is no difficulty writing similar functions which return the
popped element.</p>
<p>Moving a range of elements out of a container is more tricky:
they could be moved one at a time, but this might turn an <code>O(n)</code>
operation into an <code>O(n^2)</code> operation. A more efficient approach
is for the container to pass the removed elements to a callback, which
allows the container to delay re-establishing its invariants until
the entire range has been moved:</p>
<pre><code>template<typename Callback>
auto remove_range(const_iterator begin, const_iterator end, Callback ~ f);
</code></pre>
<p>The simple version of this would be to invoke the callback once for
each element of the range. A more powerful approach is for the argument to
the callback to be an input range generated by the container. This allows
for very efficient transfer of elements, and composability with other range
operations, but it does add complexity in the library because a new
destructive input range type must be implemented for each container.</p>
<h3>7.3. Moving objects created by <code>new</code></h3>
<p>Suppose we have a dynamically-allocated object:</p>
<pre><code>T * p = new T(makeT());
</code></pre>
<p>We might wish to destructively move from <code>*p</code>, simultaneously
releasing the memory for the object.</p>
<p>There are ways to achieve this. A tempting approach is to change the
<code>delete</code> expression to something that returns an owning reference;
then say that the memory would be released at the end of the full
expression.</p>
<p>There is a complication though: the owning reference would
have to be a <em>polymorphic</em> owning reference, at least when
<code>T</code> is a polymorphic type. Polymorphic owning references are
possible (see 8.2) but add more complexity.</p>
<p>Bearing in mind the extra complexity involved, and a seeming lack of
important use-cases for moving from <code>new</code>-ed objects, this
feature does not seem to be an essential part of destructive move.</p>
<h3>7.4. <code>std::swap</code>, <code>std::exchange</code>,
<code>std::rotate</code> etc.</h3>
<p>Ever since C++11, the default implementation of <code>std::swap</code>
has been to use move operations. Let's try replacing those with
destructive move operations:</p>
<pre><code>void swap(T & lhs, T & rhs)
{
T~ tmp = T(lifetime_cast<T~>(lhs));
new(&lhs) T(lifetime_cast<T~>(rhs));
new(&rhs) T(>>tmp);
}</code></pre>
<p>This might work, but something smells wrong. Destructive move is
supposed to be <em>safe</em> — why are we suddenly having to
resort to unsafe operations, <code>lifetime_cast</code> and placement
<code>new</code>?</p>
<p>The answer is that the unsafe look of this function is a proper
reflection of what's actually going on. It <em>is</em> unsafe to
implement <code>swap</code> this way, for at least three reasons:</p>
<ul>
<li>A destructive move operation might throw. If an exception was
thrown part-way through this function, it would lead to undefined
behaviour later when the destructor of a moved-from object is
called.</li>
<li><code>T</code> could be a polymorphic type, in which case it is
not guaranteed that the dynamic type of <code>lhs</code> or
<code>rhs</code> is the same as <code>T</code>. In that case, it
would be undefined behaviour to use a <code>lifetime_cast</code> to
create an owning reference to <code>T</code>.</li>
<li>It is possible for <code>lhs</code> and <code>rhs</code> to
alias each other. If this happens then our implementation is again
undefined behaviour.</li>
</ul>
<p>The first two points might not apply for all types <code>T</code>,
and we could explicitly check for aliasing to avoid the third issue.
However, the point remains that it is not generally possible to
implement <code>swap</code> using destructive moves.</p>
<p>The proper solution to this is that <code>swap</code> should really
be treated as a special member function. The default implementation
of <code>swap</code> in C++11 is flawed: it should do a memberwise
swap instead. Perhaps with reflection there will be a way to specify
this as a library feature. Otherwise, the rule is that
if the C++11 definition of <code>swap</code> is a bad choice for
your type then you must implement <code>swap</code> yourself.</p>
<p>So we avoid the problem with <code>swap</code> by treating it as a
special case. But this is unsatisfactory because <code>swap</code> is
only one example of a more general type of algorithm, where objects
are moved from and then reconstructed later. Other examples are
<code>std::exchange</code> and <code>std::rotate</code>. Arguably
<code>std::exchange</code> deserves to be thought of as being as
fundamental as <code>std::swap</code>, so we could special-case it again
— but this can't go on for ever.</p>
<p>Throughout this paper we have allowed relocation operations to throw.
Most of the time this is fine: a moved-from object is usually
a temporary so we don't care if it is lost. But we have now come
to a situation where it is going to cause a problem. If relocation
can throw then this kind of algorithm <em>cannot</em> be implemented with
destructive move. So, assuming that we want to implement these algorithms
in terms of destructive move where possible, we will have to fall back
to conservative move operations if destructive move could throw.
Of course, throwing moves are rare even in existing C++, and throwing
destructive moves should be even rarer, but they can exist.</p>
<p>Next we need to deal with the polymorphic base class issue. Calling
<code>std::exchange</code> for a polymorphic type would be an odd thing
to do, because it implies slicing. Similarly, an algorithm which works
on iterators such as <code>std::rotate</code> will usually work on
elements whose dynamic type is exactly the iterator reference type.
But theoretically there is nothing in the function signatures to enforce this.
So it is only possible to write these algorithms in terms of relocation
if the type is non-polymorphic or <code>final</code>. Again we have to
fall back to existing move operations otherwise, or alternatively specify
that the dynamic type is a precondition of the function.</p>
<p>Finally there may be aliasing issues like for <code>swap</code>. This
is not a problem for <code>std::exchange</code> but could be an issue
for other algorithms.</p>
<p>The conclusion of all this is that it may be possible to implement
such algorithms with destructive move, but it requires extreme care,
and may involve a fallback to conservative move operations for some types.
The good news is that most of the complexity would be in the library.
Once the basic library functions are properly implemented, more complex
algorithms can be safely built up from those.</p>
<h2><a name="Extensions">8. Extensions and known issues</a></h2>
<h3>8.1. The destructive forwarding problem</h3>
<p>Suppose we want to write a function which forwards its arguments to
a second function <code>f</code>:</p>
<pre><code>template<typename ... Args>
decltype(auto) forward_to_f(Args ~ ...args)
{
return f(>>args ...);
}
</code></pre>
<p>This uses destructive forwarding as per section 4.5.</p>
<p>There is a serious potential problem here. Suppose that <code>f</code>
happens to return a reference to one of its arguments, e.g.</p>
<pre><code>const char * f(const std::string & s) { return s.c_str(); }
</code></pre>
<p>Then the forwarding function doesn't work:</p>
<pre><code>std::cout << f("Hello"); // OK
std::cout << forward_to_f("Hello"); // UB: dangling reference!
</code></pre>
<p>In the first case, the temporary <code>string</code> lives to the end
of the full expression, so the pointer returned from <code>f</code> is
valid when it is used. But in the second case, <code>forward_to_f</code>
has an owning reference parameter, so ownership of the temporary
<code>string</code> is passed into <code>forward_to_f</code>. This means
that the <code>string</code> is destroyed before <code>forward_to_f</code>
returns, invalidating its return value.</p>
<p>So destructive forwarding is potentially dangerous. It is not necessarily
safe to take a function which uses the existing C++ forwarding mechanism,
and convert it to use destructive forwarding.</p>
<p>This is the most serious known problem for destructive move. There are
various possible approaches to fixing it, but any of them would need a
lot of additional work. Here are some possibilities:</p>
<h4>Option 1: Ignore the problem</h4>
<p>There are a few reasons why the problem is not as dangerous as it might
appear.</p>
<p>Firstly, it is already possible to see the same error in existing
C++. This happens if the function stores its arguments, intending to
forward them later: an example is the constructor of
<code>std::thread</code>. Destructive forwarding is no worse
in this case (and indeed would be an improvement in other respects).</p>
<p>Secondly, many functions forward their arguments <em>to a
constructor</em>. Examples of this type would be commonly-used functions
such as <code>std::make_shared</code> and
<code>std::vector::emplace_back</code>. In this case, destructive
perfect forwarding is theoretically dangerous, but only if the
constructed object is used as a temporary rather than being stored.
That would be rather unusual. If you write</p>
<pre><code>auto p = std::make_shared<T>(args...);</code></pre>
then <code>p</code> outlives the line of code where any temporary
objects are created, so destructive forwarding would make no difference
to whether uses of <code>p</code> are valid.</p>
<p>These two situations seem to cover the vast majority of forwarding
functions, at least in the standard library. Furthermore, we are seeing
improvements in static analysis which could catch the occasions where a
dangling reference was actually created.</p>
<p>If there are any particularly dangerous functions, they can continue
to use the existing forwarding references.</p>
<p>So it would not be unreasonable to ignore the issue.</p>
<h4>Option 2: Solve the general lifetime-extension problem</h4>
<p>C++ already has issues with object lifetimes. The most
notorious example is an attempt to use a range-based <code>for</code>
loop to iterate over a view of some temporary container. This creates
a dangling reference because, although the lifetime of the view
is extended to the scope of the loop, the lifetime of the temporary
container that it refers to is not.</p>
<p>Some attempts have been made to solve these problems
(see papers P0066 and P0936). If a general
solution was accepted, it would likely subsume the destructive
forwarding issue.</p>
<h4>Option 3: An alternative forwarding mechanism</h4>
<p>Destructive forwarding is an extension of the existing forwarding
mechanism in C++, but that has other issues as well. Perhaps an
alternative forwarding mechanism is needed?</p>
<p>Existing forwarding works by <em>capturing the exact type of
the function arguments, and calling the target function with those
exact types</em>. An alternative might be to <em>declare how the
target function is to be called, capture the signature of the target
function, and replicate this as the signature of the forwarding
function</em>.</p>
<p>This would solve the destructive forwarding problem: if the target
function had an rvalue reference parameter, then the signature of
the forwarding function would be adjusted to also have an rvalue
reference parameter. The lifetime of the argument would then be
correct.</p>
<p>Intriguingly, this would also solve other issues with forwarding,
for example the inability to forward initializer lists.</p>
<p>But even if it was clear what the semantics of this would be,
finding a convenient way to express it is a challenge. This would
be another long paper in itself.</p>
<h3>8.2. Polymorphic owning references</h3>
<p>The owning references described in this paper do not allow you
to write a virtual function where <code>*this</code> is an owning
reference. So it is not possible to destructively move from an object
when given a pointer to a polymorphic base class, without resorting to
manual memory management. (In fact the class itself would likely have
to know about the manual memory management.)</p>
<p>A solution to this would require <em>polymorphic</em> owning
references. A polymorphic owning reference manages the lifetime of an
object, but the dynamic type of the object need not be the same as the
type indicated by the reference.</p>
<p>Polymorphic owning references would have to coexist with plain
(non-polymorphic) owning references. We can't change all of our owning
references to be polymorphic, because destructuring relies on the exact
type being known. Indeed, even if polymorphic owning references did
exist, they would be extremely rarely used compared to non-polymorphic
owning references.</p>
<p>Briefly, polymorphic owning references would allow us to declare
virtual functions like this:</p>
<pre><code>struct S
{
virtual T convert_to_t() ~;
};
</code></pre>
<p>If <code>s</code> is a polymorphic owning reference to type
<code>S</code>, <code>(>>s).convert_to_t()</code> is a
virtual function call. But in the definition of the
function, <code>*this</code> is a plain (non-polymorphic) owning
reference. In order for this to be valid, the function must then be
overridden by every derived class. This mirrors how
virtual destructors work in existing C++.</p>
<h3>8.3. Other things being glossed over</h3>
<p>There are a few relatively minor issues for which discussion seems
inappropriate given that this paper is long enough already. They may need
some attention but do not seem to affect the main features required for
destructive move:</p>
<ul>
<li>Are <code>const</code> owning references useful?</li>
<li>Forwarding individual elements of parameter packs</li>
<li>Destructuring and virtual inheritance</li>
</ul>
<h2><a name="References">References</a></h2>
<ul>
<li><a href="https://github.com/sean-parent/sean-parent.github.io/wiki/Non-Proposal-for-Destructive-Move">
Sean Parent, <em>Non Proposal for Destructive Move</em></a>
</li>
<li><a href="https://quuxplusone.github.io/blog/2018/05/02/trivial-abi-101/">
Arthur O'Dwyer, <em><code>[[trivial_abi]]</code> 101</em></a>
</li>
<li><a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4158.pdf">
N4158: Pablo Halpern, <em>Destructive Move</em></a>
</li>
<li><a href="https://wg21.link/P0023r0">
P0023: Denis Bider, <em>Relocator: Efficiently moving objects</em></a>
</li>
<li><a href="https://wg21.link/P0066r0">
P0066: David Krauss, <em>Accessors and views with lifetime extension</em></a>
</li>
<li><a href="https://wg21.link/P0535">
P0535: Matthew Woehlke, <em>Generalized unpacking and parameter pack slicing</em></a>
</li>
<li><a href="https://wg21.link/P0644">
P0644: Barry Revzin, <em>Forward without <code>forward</code></em></a>
</li>
<li><a href="https://wg21.link/P0936">
P0936: Richard Smith & Nicolai Josuttis, <em>Bind Returned/Initialized
Objects to the Lifetime of Parameters</em></a>
</li>
<li><a href="https://wg21.link/P1029r0">
P1029: Niall Douglas, <em>[[move_relocates]]</em></a>
</li>
<li><a href="https://wg21.link/P1144">
P1144: Arthur O'Dwyer, <em>Object relocation in terms of move plus destroy</em></a>
</li>
<p></p>
</body>
</html>
------=_Part_1335_27732159.1547990956904--
.