www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Can I call the default opAssign after overloading opAssign?

reply "Rob T" <rob ucora.com> writes:
My understanding is that a struct will have a default postblit 
opAssign. What I want to know is if I can call the default 
opAssign after overriding it, or is it inaccessible?

I know that I do not have to execute the default after 
overriding, but if I can call it, I'd like to know because in 
some cases it may be useful to run the default. So this is just a 
general knowledge kind of question at this point.

--rt
Nov 16 2012
next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Friday, November 16, 2012 21:31:26 Rob T wrote:
 My understanding is that a struct will have a default postblit
 opAssign. What I want to know is if I can call the default
 opAssign after overriding it, or is it inaccessible?
 
 I know that I do not have to execute the default after
 overriding, but if I can call it, I'd like to know because in
 some cases it may be useful to run the default. So this is just a
 general knowledge kind of question at this point.

I don't think that it even exists. Basically, if you don't define an opAssign, one is provided for you. If you do define one, then you already have one, so the compiler doesn't provide one. - Jonathan M Davis
Nov 16 2012
prev sibling next sibling parent "Kagamin" <spam here.lot> writes:
AFAIK, opAssign and postblit are different operators. Postblit is 
called after blit, and opAssign is called instead of blit.
Nov 17 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Saturday, November 17, 2012 15:33:48 Kagamin wrote:
 AFAIK, opAssign and postblit are different operators. Postblit is
 called after blit, and opAssign is called instead of blit.

postlbit is making a new copy of an object, whereas opAssign is replacing the state of a pre-existing object. They're fundamentally different. - Jonathan M Davis
Nov 17 2012
prev sibling next sibling parent "Rob T" <rob ucora.com> writes:
I assume that when I define an opAssign, only the opAssign that I 
define gets called, which means there's no blit or postblit being 
called ever again.

I may be thoroughly confused at this point. Is there both a blit 
and a postblit, and an optional opAssign that when specified will 
override both?

--rt
Nov 18 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Monday, November 19, 2012 06:01:55 Rob T wrote:
 I assume that when I define an opAssign, only the opAssign that I
 define gets called, which means there's no blit or postblit being
 called ever again.
 
 I may be thoroughly confused at this point. Is there both a blit
 and a postblit, and an optional opAssign that when specified will
 override both?

postblit constructors and opAssign aren't really related. The postblit constructor is used when a _new_ instance is being constructed (it plays the same role as a copy constructor in C++). opAssign overloads the assignment operator and is only used when the assignment operator is used, which does _not_ happen when contstructing a new instance but only when replacing the value of an instance with that of another. S s1; S s2 = s1; // postblit s1 = s2; // opAssign foo(s1); // postblit If you don't define a postblit constructor, then when a new instance is created from another, then the original is memcpyed/blitted to the new one. If you _do_ define a postblit constructor, then the original is memcpyed/blitted and then _after_ that the postblit constructor is called so that you have the opportunity to deep copy the pieces that need to be deep copied. If you don't define opAssign, then when assigning from the one instance to another, a memcpy/blit is done to copy the data over. If you _do_ define a opAssign, then no memcpy/blit is made at all, but rather opAssign is called. - Jonathan M Davis
Nov 18 2012
prev sibling next sibling parent "Rob T" <rob ucora.com> writes:
I think you've cleared things up for me.

When I define an opAssign, I'm not really overriding a default 
opAssign, because there is none, instead I'm overriding the 
default behavior which is to perform a memcopy-like operation.

So if I defined an opAssign function, but for some odd reason I 
wanted to execute the default assignment behavior, then I can 
still do it by performing a memcopy-like operation, perhaps best 
done using the C libs memcopy function.

Correct?

--rt
Nov 18 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Monday, November 19, 2012 07:21:10 Rob T wrote:
 I think you've cleared things up for me.
 
 When I define an opAssign, I'm not really overriding a default
 opAssign, because there is none, instead I'm overriding the
 default behavior which is to perform a memcopy-like operation.
 
 So if I defined an opAssign function, but for some odd reason I
 wanted to execute the default assignment behavior, then I can
 still do it by performing a memcopy-like operation, perhaps best
 done using the C libs memcopy function.
 
 Correct?

I'm not sure. Close certainly. But if any member variables define an opAssign, then the compiler probably calls them rather than doing a simple memcpy. I'm not sure though. If it does, then a memcpy would not exhibit the same behavior, and the only way to get the same behavior would be to copy each member variable one by one. If it doesn't, then a memcpy would do the same thing as the default behavior. I am a bit worried though as to why you'd even want to skip opAssign like that. At the moment, I can't think of any legitimate use cases for doing that (though that obviously doesn't mean that you don't have one). - Jonathan M Davis
Nov 18 2012
prev sibling next sibling parent "Rob T" <rob ucora.com> writes:
On Monday, 19 November 2012 at 06:32:56 UTC, Jonathan M Davis 
wrote:
 I'm not sure. Close certainly. But if any member variables 
 define an opAssign,
 then the compiler probably calls them rather than doing a 
 simple memcpy. I'm
 not sure though. If it does, then a memcpy would not exhibit 
 the same
 behavior, and the only way to get the same behavior would be to 
 copy each
 member variable one by one. If it doesn't, then a memcpy would 
 do the same
 thing as the default behavior.

I think you are right. There's was a post a couple days ago on an issue concerning a nested struct with opAssign. The parent had no opAssign, but the nested struct did, and for some reason the nested opAssign was not being called in one case, but was being called in another. Something about being relocatable?
 I am a bit worried though as to why you'd even want to skip 
 opAssign like
 that. At the moment, I can't think of any legitimate use cases 
 for doing that
 (though that obviously doesn't mean that you don't have one).

 - Jonathan M Davis

No reason I can see at this time either. I just want to fully understand what D is doing because it's not clearly documented. What worries me most, is if I end up relying on behaviors that end up being implemented as clever compiler optimizations rather than being a part of the D language specification (which is currently MIA). --rt
Nov 19 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Monday, November 19, 2012 10:29:21 Rob T wrote:
 the D language specification (which is currently MIA).

The online documentation _is_ the official spec, though it definitely doesn't have enough detail to be unambiguous, and in some cases, it's not properly up- to-date. - Jonathan M Davis
Nov 19 2012
prev sibling next sibling parent "Dan" <dbdavidson yahoo.com> writes:
On Monday, 19 November 2012 at 05:22:38 UTC, Jonathan M Davis 
wrote:
 On Monday, November 19, 2012 06:01:55 Rob T wrote:

 postblit constructors and opAssign aren't really related. The 
 postblit
 constructor is used when a _new_ instance is being constructed 
 (it plays the
 same role as a copy constructor in C++). opAssign overloads the 
 assignment
 operator and is only used when the assignment operator is used, 
 which does
 _not_ happen when contstructing a new instance but only when 
 replacing the
 value of an instance with that of another.

Is this correct? From a implementation point of view it looks like opAssign is related to postblit in that it does call postblit first. From the spec: Struct assignment t=s is defined to be semantically equivalent to: t = S.opAssign(s); where opAssign is a member function of S: S* opAssign(S s) { ... bitcopy *this into tmp ... ... bitcopy s into *this ... ... call destructor on tmp ... return this; } It does not say postblit as well, but it does call it. When assigning one object into another it will first blit, then custom postblit if you have written one. A benefit of this is, if you want deep copy semantics and postblit does the work to provide it - you do not need an opAssign at all, as your postblit will be called. I think this is a step up over C++. The example below prints: ---------------------------------------------- Begin assign postblit A End assign ---------------------------------------------- import std.stdio; import std.traits; struct A { this(this) { c = c.dup; writeln("postblit A"); } char[] c; } struct B { A a; } struct C { B b; } struct D { C c; } void main() { D d1, d2; d1.c.b.a.c = ['a','b','c']; writeln("Begin assign"); d2 = d1; writeln("End assign"); }
Nov 19 2012
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On 11/19/12, Rob T <rob ucora.com> wrote:
 perhaps best
 done using the C libs memcopy function.

I think the safest thing you can do is: void oldAssign(Type rhs) { this.tupleof = rhs.tupleof; }
Nov 19 2012
prev sibling next sibling parent "Rob T" <rob ucora.com> writes:
On Monday, 19 November 2012 at 09:37:35 UTC, Jonathan M Davis 
wrote:
 On Monday, November 19, 2012 10:29:21 Rob T wrote:
 the D language specification (which is currently MIA).

The online documentation _is_ the official spec, though it definitely doesn't have enough detail to be unambiguous, and in some cases, it's not properly up- to-date. - Jonathan M Davis

Well yes, there is a spec, and it's pretty good in some areas, but also just not precise enough in other areas, such with what we're discussing in here. I'd like to see the mechanism surrounding the copy/move semantics described in full details as a part of the language spec, otherwise it's a bit risky to rely on these behaviors if they are only considered as compiler optimizations. I know that Walter started D from the POV of a compiler developer, so he probably does consider the optimizations to be a part of the spec, but I'd like to see that in writing somewhere to make it rock-solid official. There's mention of this in the TDPL but again it's written as being an optimization, although you could also get the impression is is a language feature, but it's not exactly clear. Note that I'm picking on this topic because it's a foundation just about everything else it built up on, so it really needs to be thoroughly documented as part of the spec. --rt
Nov 19 2012
prev sibling next sibling parent "Dan" <dbdavidson yahoo.com> writes:
On Monday, 19 November 2012 at 12:10:32 UTC, Dan wrote:

Just following up to get confirmation. Hopefully Johnathan or 
similar expert can follow up.

Here is a strong statement:

If for any struct S you implement a postblit then there is no 
need to implement opAssign to get a working assignment operator 
from a type S because by design postblit is already called by 
default opAssign. This is the behavior I see, but I may be 
missing something since the language specification does not 
mention postblit, only blit.

Thanks
Dan

 On Monday, 19 November 2012 at 05:22:38 UTC, Jonathan M Davis 
 wrote:
 On Monday, November 19, 2012 06:01:55 Rob T wrote:

 postblit constructors and opAssign aren't really related. The 
 postblit
 constructor is used when a _new_ instance is being constructed 
 (it plays the
 same role as a copy constructor in C++). opAssign overloads 
 the assignment
 operator and is only used when the assignment operator is 
 used, which does
 _not_ happen when contstructing a new instance but only when 
 replacing the
 value of an instance with that of another.

Is this correct? From a implementation point of view it looks like opAssign is related to postblit in that it does call postblit first. From the spec: Struct assignment t=s is defined to be semantically equivalent to: t = S.opAssign(s); where opAssign is a member function of S: S* opAssign(S s) { ... bitcopy *this into tmp ... ... bitcopy s into *this ... ... call destructor on tmp ... return this; } It does not say postblit as well, but it does call it. When assigning one object into another it will first blit, then custom postblit if you have written one. A benefit of this is, if you want deep copy semantics and postblit does the work to provide it - you do not need an opAssign at all, as your postblit will be called. I think this is a step up over C++. The example below prints: ---------------------------------------------- Begin assign postblit A End assign ---------------------------------------------- import std.stdio; import std.traits; struct A { this(this) { c = c.dup; writeln("postblit A"); } char[] c; } struct B { A a; } struct C { B b; } struct D { C c; } void main() { D d1, d2; d1.c.b.a.c = ['a','b','c']; writeln("Begin assign"); d2 = d1; writeln("End assign"); }

Nov 23 2012
prev sibling next sibling parent "Rob T" <rob ucora.com> writes:
On Monday, 19 November 2012 at 12:10:32 UTC, Dan wrote:
 [...]
 provide it - you do not need an opAssign at all, as your 
 postblit will be called. I think this is a step up over C++.

 The example below prints:
 ----------------------------------------------
 Begin assign
 postblit A
 End assign
 ----------------------------------------------

 import std.stdio;
 import std.traits;

 struct A {
   this(this) { c = c.dup; writeln("postblit A"); }
   char[] c;

 }
 struct B { A a; }
 struct C { B b; }
 struct D { C c; }

 void main() {
   D d1, d2;
   d1.c.b.a.c = ['a','b','c'];
   writeln("Begin assign");
   d2 = d1;
   writeln("End assign");
 }

That's VERY interesting indeed and originally I had no idea it would do this without a custom opAssign at each level. This kind of behavior *really* needs to be documented in precise detail, it's rather critical to know. --rt
Nov 23 2012
prev sibling next sibling parent "Era Scarecrow" <rtcvb32 yahoo.com> writes:
On Friday, 23 November 2012 at 22:31:46 UTC, Rob T wrote:
 That's VERY interesting indeed and originally I had no idea it 
 would do this without a custom opAssign at each level.

 This kind of behavior *really* needs to be documented in 
 precise detail, it's rather critical to know.

It IS documented. TDPL - pg. 248 [quote] The second step (the part with 'transitive field') of the postblit copy process deserves a special mention. The rationale for that behavior is [i]encapsulation[/i]-the postblit constructor of a struct object must be called even when the struct is embedded in another struct object. Consider, for example, that we make Widget a member of another struct, which in turn is a member of yet another struct: (included from pg. 246) [code] struct Widget { private int[] array; this(uint length) { array = new int[length]; } // postblit constructor this(this){ array = array.dup; } //As Before int get(size_t offset) { return array[offset]; } void set(size_t offset, int value) { array[offset] = value; } } struct Widget2 { Widget w1; int x; } struct Widget3 { Widget2 w2; string name; this(this) { name = name ~ " (copy)"; } } [/code] Now, if you want to copy around objects that contain Widgets, it would be pretty bad if the compiler forgot to properly copy the Widget subobjects. That's why when copying objects of type Widget2, a call to this(this) is issued for the w subobject, even though Widget2 does not intercept copying at all. Also, when copying objects of type Widget3, again this(this) is invoked for the field w1 of field w2. To Clarify: [code] unittest { Widget2 a; a.w1 = Widget(10); //Allocate some memory auto b = a; // this(this) called for b.w assert(a.w1.array ~is b.w1.array); // Pass Widget3 c; c.w2.w1 = Widget(20); auto d = c; // this(this) for d.w2.w1 assert(c.w.2.w.1.array !is d.w2.w1.array); //pass } [/code] [/quote]
Nov 24 2012
prev sibling next sibling parent "Dan" <dbdavidson yahoo.com> writes:
On Saturday, 24 November 2012 at 20:47:17 UTC, Era Scarecrow 
wrote:
 On Friday, 23 November 2012 at 22:31:46 UTC, Rob T wrote:
 That's VERY interesting indeed and originally I had no idea it 
 would do this without a custom opAssign at each level.

 This kind of behavior *really* needs to be documented in 
 precise detail, it's rather critical to know.

It IS documented. TDPL - pg. 248 [quote] The second step (the part with 'transitive field') of the postblit copy process deserves a special mention. The rationale for that behavior is [i]encapsulation[/i]-the postblit constructor of a struct object must be called even when the struct is embedded in another struct object. Consider, for example, that we make Widget a member of another struct, which in turn is a member of yet another struct: (included from pg. 246) [code] struct Widget { private int[] array; this(uint length) { array = new int[length]; } // postblit constructor this(this){ array = array.dup; } //As Before int get(size_t offset) { return array[offset]; } void set(size_t offset, int value) { array[offset] = value; } } struct Widget2 { Widget w1; int x; } struct Widget3 { Widget2 w2; string name; this(this) { name = name ~ " (copy)"; } } [/code] Now, if you want to copy around objects that contain Widgets, it would be pretty bad if the compiler forgot to properly copy the Widget subobjects. That's why when copying objects of type Widget2, a call to this(this) is issued for the w subobject, even though Widget2 does not intercept copying at all. Also, when copying objects of type Widget3, again this(this) is invoked for the field w1 of field w2. To Clarify: [code] unittest { Widget2 a; a.w1 = Widget(10); //Allocate some memory auto b = a; // this(this) called for b.w assert(a.w1.array ~is b.w1.array); // Pass Widget3 c; c.w2.w1 = Widget(20); auto d = c; // this(this) for d.w2.w1 assert(c.w.2.w.1.array !is d.w2.w1.array); //pass } [/code] [/quote]

Good catch on this(this) - it is documented well. But I think the questionable part is on assignment, not copy construction via postblit. For assignment the postblit *is* being called and the language spec (not TDPL) glosses over that. Also, not mentioned in TDPL is what happens if you do implement your own opAssign. I think some of the magic goes away if I'm not mistaken (i.e. those well-crafted postblits will not be called). I think this should be documented as well. Thanks Dan
Nov 24 2012
prev sibling next sibling parent "Rob T" <rob ucora.com> writes:
On Saturday, 24 November 2012 at 20:47:17 UTC, Era Scarecrow 
wrote:
 This kind of behavior *really* needs to be documented in 
 precise detail, it's rather critical to know.

It IS documented. TDPL - pg. 248 [quote]

Thanks for pointing out where the postblit stuff is documented. When I first started learning the language, I did read that part a few times over, but I found it frustratingly hard to grasp. I will re-read that section again. TDPL is a good book, but it is not the official spec, nor is it even a spec, it's a book that covers some aspects of how to use the language. How copy and assignments work in D really needs to be 100% documented in the language spec to ensure that it is officially a part of the language and not a clever compiler optimization that may or may not be implemented. To make things much clearer, the documentation should perhaps contain a flow chart showing the order of execution, with plenty of examples that show edge cases and best practices. --rt
Nov 24 2012
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Sunday, 25 November 2012 at 00:12:04 UTC, Rob T wrote:
 Thanks for pointing out where the postblit stuff is documented. 
 When I first started learning the language, I did read that 
 part a few times over, but I found it frustratingly hard to 
 grasp. I will re-read that section again.

This should be MUCH more documented. Most users (in particular C++ users) are surprised by this behavior, and creates a great deal of confusion.
 TDPL is a good book, but it is not the official spec, nor is it 
 even a spec, it's a book that covers some aspects of how to use 
 the language. How copy and assignments work in D really needs 
 to be 100% documented in the language spec to ensure that it is 
 officially a part of the language and not a clever compiler 
 optimization that may or may not be implemented.

AFAIK, there is no "official spec". And even if there was, the "de-facto" spec *is* TDPL... minus everything that could have changed since it's printing.
Nov 25 2012
prev sibling next sibling parent "Dan" <dbdavidson yahoo.com> writes:
On Sunday, 25 November 2012 at 11:05:37 UTC, monarch_dodra wrote:
 AFAIK, there is no "official spec". And even if there was, the 
 "de-facto" spec *is* TDPL... minus everything that could have 
 changed since it's printing.

I think TDPL is great, but there is a doc called "D Language Specification" which is the perfect level for reference - it is just now outdated and incomplete. Couldn't Walter just open source this document and retain final say on the updates? With C++ we had the ARM and Stroustrup's book. For D we have the spec and TDPL. Put the source of the language spec out in some markup language, let others help to bring it up to snuff, generate pdf's and other formats. The only tricky part is documenting areas where the language designer(s) has open issues without a commitment to a solution. Take the opAssign issue in question. In TDPL 7.1.5.1 we have "Recall that Widget holds a private int[] member that was supposed to be distinct for each Widget object. Assigning w2 to w1 field by field assigns w2.array to w1.array—a simple assignment of array bounds, without actually copying the array contents. This needs fixing because what we want is to create a duplicate of the array in the source Widget and assign that duplicate to the target Widget." He then goes on to describe how user code can intercept opAssign to make things right. But, unless I'm missing something, that is unnecessary because the default opAssign carries the call to postblit (see previous example). Now look at the issue from the language spec (Structs & Unions - Assignment Overload): Struct assignment t=s is defined to be semantically equivalent to: t = S.opAssign(s); where opAssign is a member function of S: S* opAssign(S s) { ... bitcopy *this into tmp ... ... bitcopy s into *this ... ... call destructor on tmp ... return this; } This description is almost perfect, it just fails to mention "...bitcopy and postblit". Both docs are incorrect according to the behavior of default opAssign. I think the language spec should be fixed first and kept accurate. Thanks Dan
Nov 25 2012
prev sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, November 25, 2012 12:05:36 monarch_dodra wrote:
 AFAIK, there is no "official spec". And even if there was, the
 "de-facto" spec *is* TDPL... minus everything that could have
 changed since it's printing.

The online docs are the official spec. They're just not necessarily complete or up-to-date, making it so that there is no definitive answer when the compiler, the spec, and/or TDPL conflict. _Usually_ TDPL wins out on such arguments, but not always. Regardless, the online docs clearly need to be improved before you can implement a compiler for the language based purely on the spec. - Jonathan M Davis
Nov 25 2012