April 04, 2018
On 4/1/2018 3:49 AM, bachmeier wrote:
> What I was wondering too. I mean, breaking changes just don't happen to this language. Now there will be, without even an indication of how existing code would have to be rewritten, or how this large-scale breakage is different than the breakages that just can't happen because reasons. I guess that's why there's always the disclaimer, "We'll only break code if there's a really good reason." That reason is "in case we want to".

The idea is not to introduce a breaking change. Postblits will remain. The copy constructor will be an additional construct, and if one is defined, it will be preferred.

Eventually (years later) postblits will be slowly deprecated and eventually removed.
April 04, 2018
On Wednesday, 4 April 2018 at 22:30:39 UTC, Walter Bright wrote:
> On 4/1/2018 3:49 AM, bachmeier wrote:
>> What I was wondering too. I mean, breaking changes just don't happen to this language. Now there will be, without even an indication of how existing code would have to be rewritten, or how this large-scale breakage is different than the breakages that just can't happen because reasons. I guess that's why there's always the disclaimer, "We'll only break code if there's a really good reason." That reason is "in case we want to".
>
> The idea is not to introduce a breaking change. Postblits will remain. The copy constructor will be an additional construct, and if one is defined, it will be preferred.
>
> Eventually (years later) postblits will be slowly deprecated and eventually removed.

Could you describe how a copy constructor differs from postblit ?
April 04, 2018
On Wed, Apr 04, 2018 at 03:30:39PM -0700, Walter Bright via Digitalmars-d wrote:
> On 4/1/2018 3:49 AM, bachmeier wrote:
> > What I was wondering too. I mean, breaking changes just don't happen to this language. Now there will be, without even an indication of how existing code would have to be rewritten, or how this large-scale breakage is different than the breakages that just can't happen because reasons. I guess that's why there's always the disclaimer, "We'll only break code if there's a really good reason." That reason is "in case we want to".
> 
> The idea is not to introduce a breaking change. Postblits will remain. The copy constructor will be an additional construct, and if one is defined, it will be preferred.

This may lead to trouble, unless we explicitly make it illegal for something to have both a postblit and a copy ctor. Having the two interact with each other seems likely to lead to a lot of pathological, hard-to-understand results.


> Eventually (years later) postblits will be slowly deprecated and
> eventually removed.

Actually, if it's possible to have them coexist and there are no disadvantages to doing so, why not just leave both of them in?  Then cases where postblits give the best performance could just be left as-is, and cases that require a copy ctor can use a copy ctor.


T

-- 
Klein bottle for rent ... inquire within. -- Stephen Mulraney
April 04, 2018
On Wednesday, April 04, 2018 22:41:39 Stefan Koch via Digitalmars-d wrote:
> On Wednesday, 4 April 2018 at 22:30:39 UTC, Walter Bright wrote:
> > On 4/1/2018 3:49 AM, bachmeier wrote:
> >> What I was wondering too. I mean, breaking changes just don't happen to this language. Now there will be, without even an indication of how existing code would have to be rewritten, or how this large-scale breakage is different than the breakages that just can't happen because reasons. I guess that's why there's always the disclaimer, "We'll only break code if there's a really good reason." That reason is "in case we want to".
> >
> > The idea is not to introduce a breaking change. Postblits will remain. The copy constructor will be an additional construct, and if one is defined, it will be preferred.
> >
> > Eventually (years later) postblits will be slowly deprecated
> > and eventually removed.
>
> Could you describe how a copy constructor differs from postblit ?

With a postblit, all of the members are copied _before_ the postblit constructor is called, meaning that the struct was blitted, and then the postblit constructors were called for each member variable that has a postblit constructor, before the struct's postblit constructor is called. So, inside the postblit constructor, you're mutating an already copied object. It's doing work post the copy - hence postblit.

With a copy constructor, the copy is being directly constructed. Each member will be initialized exactly once rather than being initialized and then mutated. Ideally, each member that was not explicitly initialized by the copy constructor would be automatically copied from the original so that you wouldn't have to explicitly copy each member, so syntactically, it may look very much like a postblit constructor, but the key difference is that with a copy constructor, the copy is directly constructed with whatever changes are done by the copy constructor as opposed to having it copied and then mutated.

- Jonathan M Davis

April 04, 2018
On Wednesday, April 04, 2018 15:51:25 H. S. Teoh via Digitalmars-d wrote:
> On Wed, Apr 04, 2018 at 03:30:39PM -0700, Walter Bright via Digitalmars-d
wrote:
> > On 4/1/2018 3:49 AM, bachmeier wrote:
> > > What I was wondering too. I mean, breaking changes just don't happen to this language. Now there will be, without even an indication of how existing code would have to be rewritten, or how this large-scale breakage is different than the breakages that just can't happen because reasons. I guess that's why there's always the disclaimer, "We'll only break code if there's a really good reason." That reason is "in case we want to".
> >
> > The idea is not to introduce a breaking change. Postblits will remain. The copy constructor will be an additional construct, and if one is defined, it will be preferred.
>
> This may lead to trouble, unless we explicitly make it illegal for something to have both a postblit and a copy ctor. Having the two interact with each other seems likely to lead to a lot of pathological, hard-to-understand results.

+1

> > Eventually (years later) postblits will be slowly deprecated and
> > eventually removed.
>
> Actually, if it's possible to have them coexist and there are no disadvantages to doing so, why not just leave both of them in?  Then cases where postblits give the best performance could just be left as-is, and cases that require a copy ctor can use a copy ctor.

The big disadvantage is that you would have to deal with two distinct ways to copy objects and would have to explain them both to everyone learnig D. AFAIK, the only real advantage to a postblit constructor is that you don't have to explicitly copy everything like you do when declaring a copy constructor in C++ - you just change the parts that need to be changed after the copy is made in order to make it a deep copy or do whatever you need to do to finish making the copy what it should be. As such, if we define copy constructors in a way that only requires listing the members that are actually going to be different from the default copy, then I really don't see an advantage to postblit constructors.

Hmmm. And actually, thinking about that, I almost wonder if we could just change this(this) to be a copy constructor. Assume for a moment that inside this(this), we provide a new symbol that is the this pointer/reference for the original. We then have make it so that any member variable in the newly constructed object which is read before it is assigned is default-copied. As such,

this(this)
{
}

would just default-copy everything basically as it does now. And if you currently had

this(this)
{
    _foo = _foo.dup;
}

then after the change, it would still have the same semantics, because the _foo is used before it is assigned, so it must be default-copied. However, if we provide the object being copied via an invisible ref (like this) called orig, doing

this(this)
{
    _foo = orig._foo;
}

then that would directly initialize _foo without it being default-copied. As such, existing postblit constructors should continue to work, but it would be straightforward to turn them into copy constructors, and most of the changes would be underneath the hood.

I don't know if orig is the best name (and making it a keyword would be really annoying, since it's a name that I use all the time), but on the surface at least, the idea seems sound to me. And assuming that there isn't a big implementation issue that makes it a serious problem, it should actually make transitioning from postblit constructors to copy constructors very clean and negate the need for any kind of deprecation process.

- Jonathan M Davis

April 05, 2018
On Tuesday, 3 April 2018 at 11:36:27 UTC, ag0aep6g wrote:
> Maybe. But we can't explain the special assignment semantics with it being initialization.
>
> Or can we?

Postblit is a part of initialization of a copy. Until postblit completes the object is in invalid state, same as in constructor.
April 05, 2018
On Wed, Apr 04, 2018 at 05:08:26PM -0600, Jonathan M Davis via Digitalmars-d wrote: [...]
> AFAIK, the only real advantage to a postblit constructor is that you don't have to explicitly copy everything like you do when declaring a copy constructor in C++ - you just change the parts that need to be changed after the copy is made in order to make it a deep copy or do whatever you need to do to finish making the copy what it should be. As such, if we define copy constructors in a way that only requires listing the members that are actually going to be different from the default copy, then I really don't see an advantage to postblit constructors.

That's a very interesting concept.  It could be a nice way of bridging current semantics over to the new semantics.  Some of the existing postblit code might even be usable as-is, when reinterpreted in this way!


> Hmmm. And actually, thinking about that, I almost wonder if we could just change this(this) to be a copy constructor. Assume for a moment that inside this(this), we provide a new symbol that is the this pointer/reference for the original. We then have make it so that any member variable in the newly constructed object which is read before it is assigned is default-copied. As such,
> 
> this(this)
> {
> }
> 
> would just default-copy everything basically as it does now.

Why even bother with declaring an empty this(this)?  Just leave it out, and the compiler assumes default-copy.


> And if you currently had
> 
> this(this)
> {
>     _foo = _foo.dup;
> }
> 
> then after the change, it would still have the same semantics, because the _foo is used before it is assigned, so it must be default-copied.

That's a pretty neat way of transitioning from current semantics to new semantics.  I like it.


> However, if we provide the object being copied via an invisible ref
> (like this) called orig, doing
> 
> this(this)
> {
>     _foo = orig._foo;
> }
> 
> then that would directly initialize _foo without it being default-copied. As such, existing postblit constructors should continue to work, but it would be straightforward to turn them into copy constructors, and most of the changes would be underneath the hood.
[...]

I like this idea.  Except the syntax could be improved:

	this(this orig)		// <-- N.B.
	{
		_foo = orig._foo;
	}

Let the user specify the symbol, instead of introducing yet another implicit magic identifier. :-)


T

-- 
"I suspect the best way to deal with procrastination is to put off the procrastination itself until later. I've been meaning to try this, but haven't gotten around to it yet. " -- swr
April 05, 2018
On Thursday, April 05, 2018 11:46:25 H. S. Teoh via Digitalmars-d wrote:
> On Wed, Apr 04, 2018 at 05:08:26PM -0600, Jonathan M Davis via Digitalmars-d wrote: [...]
> > Hmmm. And actually, thinking about that, I almost wonder if we could just change this(this) to be a copy constructor. Assume for a moment that inside this(this), we provide a new symbol that is the this pointer/reference for the original. We then have make it so that any member variable in the newly constructed object which is read before it is assigned is default-copied. As such,
> >
> > this(this)
> > {
> > }
> >
> > would just default-copy everything basically as it does now.
>
> Why even bother with declaring an empty this(this)?  Just leave it out, and the compiler assumes default-copy.

You wouldn't declare it. I was just talking about it to discuss the semantics.

> > However, if we provide the object being copied via an invisible ref
> > (like this) called orig, doing
> >
> > this(this)
> > {
> >
> >     _foo = orig._foo;
> >
> > }
> >
> > then that would directly initialize _foo without it being default-copied. As such, existing postblit constructors should continue to work, but it would be straightforward to turn them into copy constructors, and most of the changes would be underneath the hood.
>
> [...]
>
> I like this idea.  Except the syntax could be improved:
>
>   this(this orig)     // <-- N.B.
>   {
>       _foo = orig._foo;
>   }
>
> Let the user specify the symbol, instead of introducing yet another implicit magic identifier. :-)

Well, that might be better in that it allows the user to choose the name, but it then opens up the question of whether this(this) should be deprecated, and part of the point was to try and just do it all in-place and simply turn postblit constructors into copy constructors - though it could be argued that this(this) was just a copy constructor where you didn't bother to use the original, and anyone actually wanting to take advantage of copy constructors is going to need to make changes anyway. But if this(this) simply becomes a copy constructor (whether a default name is used or something like this(this orig) is used to define the name), then we hopefully would be able to avoid actually deprecating anything and just make more code work and making it so that only minor tweaks are necessary to actually take full advantage of it being a copy constructor instead of mutating the copy in the copy constructor.

The other issue that occurred to me after posting about this was the question of what to do about const. Ideally, this(this) or this(this orig) would work just fine with const (assuming that it didn't do anything that would mutate the object), but someone might want to actually require const or overload the copy constructor for the const case, in which case, I was thinking making this(const this), but with your suggestion, I guess that it would become either this(const this orig) or this(this const orig). I don't know. As usual, adding const into the mix makes things messier, but part of the point of all of this is to make it so that we can copy arbitrarily complex const objects.

In any case, I think that the idea of trying to simply turn postblit constructors into copy constructors has merit. I certainly don't think that we need to be doing anything like

struct S
{
    this(ref S orig)
    {
        ...
    }
}

where everyone will have to convert their postblits over time in order to avoid the deprecation and where it actually risks conflicting with existing constructors.

- Jonathan M Davis

April 11, 2018
On Thursday, 5 April 2018 at 18:46:25 UTC, H. S. Teoh wrote:
> I like this idea.  Except the syntax could be improved:
>
> 	this(this orig)		// <-- N.B.
> 	{

It's already valid syntax, equivalent to `this(typeof(this) orig)`. It can be called explicitly, but is ignored for implicit construction. Perhaps we will just enable `this(typeof(this) orig)` for implicit construction too, if there's no postblit defined.
April 13, 2018
On Wednesday, 4 April 2018 at 10:37:57 UTC, Steven Schveighoffer wrote:
> I would like to see more flexibility for copying.

It's a tradeoff between control and ergonomics.

>> import std.stdio;
>> 
>> immutable(int)* p;
>> 
>> struct S
>> {
>>      int x;
>>      this(this) immutable
>>      {
>>          x = 42; /* First write. */
>>          .p = &this.x;
>>          writeln(p, " ", *p); /* Prints some address and 42. */
>>      }
>> }
>> 
>> struct T
>> {
>>      S s;
>>      this(this) immutable
>>      {
>>          s = S(13); /* Second write. Breaking immutable? */
>
> Of course this doesn't compile, because s is considered immutable by now. What I was saying is that we can't allow postblit to modify data that has already been postblitted, because of the reason this example is showing.

Assignment ends lifetime of the previous value, it will be just a dangling pointer.

AFAIK this is how reassignment works wrt destructors:

{
  immutable tmp=S(13);
  s.__dtor(); //if any
  s=tmp; //move into immutable storage
}

> I don't think you should be able to write to the same field multiple times in an immutable/const constructor. If so, that's a bug.

Stores to const fields are special, so it should be possible to track them in postblits too.