<div dir="auto"><div><div class="gmail_quote"><div dir="ltr">On Thu., 26 Jul. 2018, 2:45 am RazvanN via Digitalmars-d, <<a href="mailto:digitalmars-d@puremagic.com">digitalmars-d@puremagic.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">Hello everyone!<br>
<br>
As you probably know, I am working on the copy constructor DIP <br>
and implementation. So far, I managed to implement 95% of the <br>
copy constructor logic (as stated in the DIP). The point that is <br>
a bit of a headache is how/when should opAssign be generated when <br>
a copy constructor is defined. Now here is what I have (big <br>
thanks to Andrei for all the ideas, suggestions and brainstorms):<br>
<br>
-> mutability of struct fields:<br>
<br>
If the struct contains any const/immutable fields, it is <br>
impossible to use the copy constructor for opAssign, because the <br>
copy constructor might initialize them. Even if the copy <br>
constructor doesn't touch the const/immutable fields the compiler <br>
has to analyze the function body to know that, which is <br>
problematic in situations when the body is missing. => opAssign <br>
will be generated when the struct contains only assignable <br>
(mutable) fields.<br>
<br>
-> qualifiers:<br>
<br>
The copy constructor signature is : `@implicit this(ref $q1 S <br>
rhs) $q2`, where q1 and q2 represent the qualifiers that can be <br>
applied to the function and the parameter (const, immutable, <br>
shared, etc.). The problem that arises is: depending on the <br>
values of $q1 and $q2 what should the signature of opAssign be?<br>
<br>
A solution might be to generate for every copy constructor <br>
present its counterpart opAssign: `void opAssign(ref $q1 S rhs) <br>
$q2`. However, when is a const/immutable opAssign needed? There <br>
might be obscure cases when that is useful, but those are niche <br>
situations where the user must step it and clarify what the <br>
desired outcome is and define its own opAssign. For the sake of <br>
simplicity, opAssign will be generated solely for copy <br>
constructors that have a missing $q2 = ``.<br>
<br>
-> semantics in the presence of a destructor:<br>
<br>
If the struct that has a copy constructor does not define a <br>
destructor, it is easy to create the body of the above-mentioned <br>
opAssign: the copy constructor is called and that's that:<br>
<br>
void opAssign(ref $q1 S rhs)    // version 1<br>
{<br>
     S tmp = rhs;        // copy constructor is called<br>
     memcpy(this, tmp);  // blit it into this<br>
}</blockquote></div></div><div dir="auto"><br></div><div dir="auto">Why the memcpy?</div><div dir="auto">This looks inefficient.</div><div dir="auto"><br></div><div dir="auto">Is it in case the constructor throws?</div><div dir="auto">Have a 'nothrow' case where it constructs directly to this?</div><div dir="auto"><br></div><div dir="auto"></div><div dir="auto"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
Things get interesting when a destructor is defined, because now <br>
we also have to call it on the destination:<br>
<br>
void opAssign(ref $q1 S rhs)   // version 2<br>
{<br>
    this.__dtor;           // ensure the dtor is called<br>
    memcpy(this, S.init)   // bring the object in the initial state<br>
    this.copyCtor(rhs);    // call constructor on object in .init <br>
state<br>
}<br>
<br>
The problem with the above solution is that it does not take into <br>
account the fact<br>
that the copyCtor may throw and if it does, then the object will <br>
be in a partially initialized state. In order to overcome this, <br>
two temporaries are used:<br>
<br>
void opAssign(ref $q1 S rhs)    // version 3<br>
{<br>
     S tmp1 = rhs;                // call copy constructor<br>
     void[S.sizeof] tmp2 = void;<br>
<br>
     // swapbits(tmp1, this);<br>
     memcpy(tmp2, this);<br>
     memcpy(this, tmp1);<br>
     memcpy(tmp1, tmp2);<br>
<br>
     tmp1.__dtor();<br>
}<br>
<br>
In this version, if the copy constructor throws the object will <br>
still be in a valid state.<br>
<br>
-> attribute inference for the generated opAssign:<br>
<br>
For version 1: opAssign attributes are inferred based on the copy <br>
constructor attrbiutes.<br>
For version 2: opAssign attributes are inferred based on copy <br>
constructor and destructor attributes<br>
For version 3: the declaration of the void array can be put <br>
inside a trusted block and then attributes are inferred based on <br>
copy constructor and destructor attributes<br>
<br>
If the copy constructor is marked `nothrow` and the struct <br>
defines a destructor, then version 2 is used, otherwise version 3.<br>
<br>
What are your thoughts on this?<br>
<br>
RazvanN</blockquote></div></div><div dir="auto"><br></div><div dir="auto"></div><div dir="auto">This all looks about right to me!</div><div dir="auto">I doubt there are any alternative options.</div><div dir="auto"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
</blockquote></div></div></div>