www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Time to kill T() as (sometimes) working T.init alias ?

reply Dmitry Olshansky <dmitry.olsh gmail.com> writes:
There was a topic on this NG before by deadalnix but apparently failed 
to penetrate the masses.I've brought some unpleasant facts together to 
get it a death warrant.

1. Quick intro. An example of surprise factor with T() and templated 
constructor.

import std.conv;
struct A{
    int[] padded;
    this(T...)(T args){
     padded = [0];
     foreach(i, v; T)
         padded ~= to!int(args[i]);
     padded ~= 0;
    }
//...
}

unitest{
     A a = A(2, 3);
     assert(a.padded == [0, 2, 3, 0]);
     A b = A(1);
     assert(b.padded == [0, 1, 0]);
     A c = A();
     assert(c.padded is null); //Spoiler: it passes ;)
}

Enjoy the nice pitfall. Now if it was inside generic function then you'd 
better check if your type-tuple is empty because compiler will go on 
limb and substitute it with T.int:

void someFunc(T...)(T args)
{
    ...
    //somewhere we need to pass some of args to construct A
    A x = A(args[1..$]); //may or may not call constructor depending on args
}

For more real world example - it happens with Phobos containers - you 
can't create an empty one. Either T.init or non-zero argument list.
See e.g. this pull https://github.com/D-Programming-Language/phobos/pull/953

2. Let's go a bit further with generic types. Suppose we remove T() as 
T.init. What if it breaks somebody's rigorously maintained code?
Surprise - this notation doesn't even exist for built-in types (unlike 
T.init). Say with:

int fooTest(T)(){
     T test = T();
     return 3;
}

//next line fails to compile with:
//Error: function expected before (), not int of type int
static assert(fooTest!int() == 3);

static assert(fooTest!A() == 3); //OK

3. Even further any code relying on some arbitrary (but not built-in!) 
type to have T() return T.init is broken in many ways as there is static 
opCall, that instead can say... order you a burrito (depending on 
author's maliciousness) ?
Simply put there is not even a single guarantee that static opCall of 
type T will return that particular type.

After killing this ugly craft the suggestion is to, of course, introduce 
0-argument constructors. The syntax is already there. static opCall 
can't prevent introduction of 0-arg constructor as the compiler already 
has to disambiguate between the static opCall and the ctor. (BTW which 
one wins?)

It'll also help immensely people that currently use static opCall to 
emulate 0-arg ctor and thus use convention rather then guarantee that it 
does the job of 0-arg constructor.

Thoughts?

-- 
Dmitry Olshansky
Nov 25 2012
next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 11/25/2012 05:47 PM, Dmitry Olshansky wrote:
 After killing this ugly craft the suggestion is to, of course, introduce
0-argument constructors.

I think it should be done, but then it is even less clear how .init should work.
  static opCall can't prevent introduction of 0-arg constructor as the compiler
already has to disambiguate between the static opCall and the ctor. (BTW which
one wins?)

It does not disambiguate. I think constructors and static opCall should just overload against each other, The current behaviour in case there are both opCall and constructors is to attempt to pick opCall in case the number of arguments is zero and the constructors otherwise.
 ...

 Thoughts?

1. is valid. 2./3. are examples of insufficient template constraints. If the suggestion is to get rid of the built-in struct 0-arg default constructor iff the user provides his own constructors, as is done with the more than 0-arg default constructors, then I fully agree.
Nov 25 2012
parent Dmitry Olshansky <dmitry.olsh gmail.com> writes:
11/25/2012 9:25 PM, Timon Gehr пишет:
 On 11/25/2012 05:47 PM, Dmitry Olshansky wrote:
 After killing this ugly craft the suggestion is to, of course,
 introduce 0-argument constructors.

I think it should be done, but then it is even less clear how .init should work.

I suppose that T.init is the blank object, that is composed of respective initial values of its fields. Initial value is the one specified during declaration of a field or .init of its type. You mean the more or less working disable this(); ? That might need extra consideration.
  static opCall can't prevent introduction of 0-arg constructor as the
 compiler already has to disambiguate between the static opCall and the
 ctor. (BTW which one wins?)

It does not disambiguate. I think constructors and static opCall should just overload against each other,

I can't think of anything more logical then to do just that.
 The current behaviour in case there are both opCall and constructors is
 to attempt to pick opCall in case the number of arguments is zero and
 the constructors otherwise.


 ...

 Thoughts?

1. is valid. 2./3. are examples of insufficient template constraints.

Agreed. The point of second however was mostly about built-ins not having this way of default construction. Thus T() is quite brittle in generic code (if the default construction is implied).
 If the suggestion is to get rid of the built-in struct 0-arg default
 constructor iff the user provides his own constructors, as is done with
 the more than 0-arg default constructors, then I fully agree.

Indeed, I haven't thought of automatically provided per-field constructors. I like your suggestion to do the same with 0-arg ones - provided automatic one returning T.init. Should be more consistent and even backwards compatible I guess. -- Dmitry Olshansky
Nov 25 2012
prev sibling next sibling parent reply "monarch_dodra" <monarchdodra gmail.com> writes:
On Sunday, 25 November 2012 at 16:47:08 UTC, Dmitry Olshansky 
wrote:
 Thoughts?

I don't know about "killing" T(), but I think there *needs* to be an (easy) mechanism to declare ***and*** run-time initialize an object, in a single and comprehensive line. I had proposed something 2 months ago here: http://forum.dlang.org/thread/bvuquzwfykiytdwsqkky forum.dlang.org The proposal wasn't perfect, but still. We need to figure something out...
Nov 28 2012
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 11/29/2012 4:47 AM, monarch_dodra wrote:
 On Sunday, 25 November 2012 at 16:47:08 UTC, Dmitry Olshansky wrote:
 Thoughts?

I don't know about "killing" T(), but I think there *needs* to be an (easy) mechanism to declare ***and*** run-time initialize an object, in a single and comprehensive line. I had proposed something 2 months ago here: http://forum.dlang.org/thread/bvuquzwfykiytdwsqkky forum.dlang.org The proposal wasn't perfect, but still. We need to figure something out...

The original idea is that there should be *no such thing* as default construction of a struct as being anything other than T.init. The default construction of a struct should be a compile time creature, not a runtime one. Any methods or workarounds to try and make T() produce something different from T.init is bad D practice. The compiler tries to statically head them off, but probably should do a better job of that.
Nov 28 2012
next sibling parent reply Dmitry Olshansky <dmitry.olsh gmail.com> writes:
11/29/2012 7:24 AM, Walter Bright пишет:
 On 11/29/2012 4:47 AM, monarch_dodra wrote:
 On Sunday, 25 November 2012 at 16:47:08 UTC, Dmitry Olshansky wrote:
 Thoughts?

I don't know about "killing" T(), but I think there *needs* to be an (easy) mechanism to declare ***and*** run-time initialize an object, in a single and comprehensive line. I had proposed something 2 months ago here: http://forum.dlang.org/thread/bvuquzwfykiytdwsqkky forum.dlang.org The proposal wasn't perfect, but still. We need to figure something out...

The original idea is that there should be *no such thing* as default construction of a struct as being anything other than T.init. The default construction of a struct should be a compile time creature, not a runtime one.

constructed object. Just don't make T() mean it, please! There are a 0-argument constructor or rather a run-time construction that need no arguments. Examples are auto-seeded PRNG, empty containers, digest that typically use default initial vector etc. Plenty of things. If classes have it why shouldn't structs have one as well? Is there any reason to introduce this discrepancy? The sudden change of user-defined run-time construction to a biltblit a T.init mask once number of arguments is 0 is a nasty surprise and is a high irregularity in the language. The unwary may hit a brick wall quite suddenly. Even better example are constructors with all default args: //simplified struct A{ int[] values; this(int size=10) //default size... { values = new int[size]; } //... } A a = A(20); //okay A b = A(); //try to pick defaults... assert(b.values is null); // passes - WTF?
 Any methods or workarounds to try and make T() produce something
 different from T.init is bad D practice.  The compiler tries to
 statically head them off, but probably should do a better job of that.

Why do we have to use T() syntax for this? It blocks 0-argument constructor & ones with all defaults. Default construction is done either way unless avoided with =void and there is always a T.init. I've spent quite some breath trying to show how T() is unhelpful in any case one needs a default constructed object. -- Dmitry Olshansky
Nov 29 2012
next sibling parent Nick Treleaven <ntrel-public yahoo.co.uk> writes:
On 29/11/2012 16:27, Dmitry Olshansky wrote:
 11/29/2012 7:24 AM, Walter Bright пишет:
 The original idea is that there should be *no such thing* as default
 construction of a struct as being anything other than T.init. The
 default construction of a struct should be a compile time creature, not
 a runtime one.

constructed object. Just don't make T() mean it, please! There are a 0-argument constructor or rather a run-time construction that need no arguments. Examples are auto-seeded PRNG, empty containers, digest that typically use default initial vector etc. Plenty of things. If classes have it why shouldn't structs have one as well? Is there any reason to introduce this discrepancy? The sudden change of user-defined run-time construction to a biltblit a T.init mask once number of arguments is 0 is a nasty surprise and is a high irregularity in the language. The unwary may hit a brick wall quite suddenly. Even better example are constructors with all default args: //simplified struct A{ int[] values; this(int size=10) //default size... { values = new int[size]; } //... } A a = A(20); //okay A b = A(); //try to pick defaults... assert(b.values is null); // passes - WTF?
 Any methods or workarounds to try and make T() produce something
 different from T.init is bad D practice.  The compiler tries to
 statically head them off, but probably should do a better job of that.

Why do we have to use T() syntax for this? It blocks 0-argument constructor & ones with all defaults. Default construction is done either way unless avoided with =void and there is always a T.init. I've spent quite some breath trying to show how T() is unhelpful in any case one needs a default constructed object.

Totally agree with all this. I can't understand why T() should 'default construct' T - it's not default anything if you explicitly type T(). static opCall is an ugly workaround especially in generic wrapper code. Not allowing 0-argument constructors is unintuitive. I have several times tried reading explanations of why it's like this to no avail. If you are allowed to call other runtime constructors, why not zero-arg ones? struct A{...} A a; // fine, default construct a A a = A(); // why does this mean the same thing??? I wish we could deprecate the second syntax as we really need to repurpose it.
Nov 29 2012
prev sibling parent reply Dmitry Olshansky <dmitry.olsh gmail.com> writes:
11/30/2012 3:31 AM, Jonathan M Davis пишет:
 On Thursday, November 29, 2012 20:27:32 Dmitry Olshansky wrote:
 11/29/2012 7:24 AM, Walter Bright пишет:
 On 11/29/2012 4:47 AM, monarch_dodra wrote:
 On Sunday, 25 November 2012 at 16:47:08 UTC, Dmitry Olshansky wrote:
 Thoughts?

I don't know about "killing" T(), but I think there *needs* to be an (easy) mechanism to declare ***and*** run-time initialize an object, in a single and comprehensive line. I had proposed something 2 months ago here: http://forum.dlang.org/thread/bvuquzwfykiytdwsqkky forum.dlang.org The proposal wasn't perfect, but still. We need to figure something out...

The original idea is that there should be *no such thing* as default construction of a struct as being anything other than T.init. The default construction of a struct should be a compile time creature, not a runtime one.

Okay let it be. I'm not against having a defined blank default constructed object. Just don't make T() mean it, please!

I'm all for T() meaning T.init if T doesn't have a static opCall, but T() shouldn't be guaranteed to be T.init. I'd very much like to see code like auto t = T(); to continue to work regardless of whether T has a static opCall or not.

And what you'd expect 't' to be then? And why such code is useful anyway? The only sane way I see is to make it an explicit call of 0-arg constructor then one can safely assume: 1. t is of type T 2. t is properly constructed and not some invalid state like T.init may be Currently with opCall it could be anything otherwise it ends up T.init or compiler error if T is a built-in type.
 But it
 should be able to have a static opCall, and T() should work if it doesn't have
 one. Assuming that T() means T.init makes no sense.

Why in the nine hells we have static opCall to begin with? Probably to workaround 0-argument ctor situation. I haven't seen a better or at least sensible use case. Yet it has great potential for abuse. (and the fact that compiler picks opCall first (static or not) instead of constructor doesn't help matters much) -- Dmitry Olshansky
Nov 30 2012
parent reply Dmitry Olshansky <dmitry.olsh gmail.com> writes:
12/1/2012 1:38 AM, Jonathan M Davis пишет:
 On Friday, November 30, 2012 23:34:04 Dmitry Olshansky wrote:
 11/30/2012 3:31 AM, Jonathan M Davis пишет:
 I'm all for T() meaning T.init if T doesn't have a static opCall, but T()
 shouldn't be guaranteed to be T.init. I'd very much like to see code like

 auto t = T();

 to continue to work regardless of whether T has a static opCall or not.

And what you'd expect 't' to be then? And why such code is useful anyway? The only sane way I see is to make it an explicit call of 0-arg constructor then one can safely assume: 1. t is of type T 2. t is properly constructed and not some invalid state like T.init may be Currently with opCall it could be anything otherwise it ends up T.init or compiler error if T is a built-in type.

If auto t = T(); works then you don't have to care whether the type has a static opCall or not. You get the most valid default-constructed object that there is (or at least, the closest thing that there is to a default-constructed object). If that's init, then it's init. If it's static opCall, then it's static opCall. I don't want to have to care which it is. Also, if I see T t; I'm likely to think that was supposed to be initialized, but the programmer forgot, whereas with

T t = T.init; for when you want explicitness. Other then this I've come to rely on T t; being default constructed and not rising an eyebrow. It's only in context such as these 3 lines : T x = blah; U y = bleh; W w; That I'd think w == T.init might be unintended.
 auto t = T();

 it's clear that that it was intended to be initialized to whatever T() is (be
 it T.init or the result of a static opCall), and it's clear that the
 programmer didn't forget to initialize it.

Either way my final proposal doesn't remove it. In short it goes as follows: - allow 0-arg constructors for consistency and cross-cutting issues (like all default args ctor) - use a default one (as is done with per-field constructors) if none or user defined match. The default will blit object with T.init. It'd let static opCall be there as is. It will just put them in the same overload set as constructors. -- Dmitry Olshansky
Dec 01 2012
parent Dmitry Olshansky <dmitry.olsh gmail.com> writes:
 Either way my final proposal doesn't remove it. In short it goes as
 follows:

 - allow 0-arg constructors for consistency and cross-cutting issues
 (like all default args ctor)
 - use a default one (as is done with per-field constructors) if none or
 user defined match. The default will blit object with T.init.

... if none OF user-defined match. -- Dmitry Olshansky
Dec 01 2012
prev sibling next sibling parent Dmitry Olshansky <dmitry.olsh gmail.com> writes:
11/29/2012 9:12 PM, jerro пишет:
 The original idea is that there should be *no such thing* as default
 construction of a struct as being anything other than T.init. The
 default construction of a struct should be a compile time creature,
 not a runtime one.

 Any methods or workarounds to try and make T() produce something
 different from T.init is bad D practice. The compiler tries to
 statically head them off, but probably should do a better job of that.

The only reason to associate no parameter constructors with default values is because of how C++ works. There is no reason why Foo foo; (1) should be equivalent to auto foo = Foo(); (2) in D. We could allow constructors with no parameters and make (1) equivalent to auto foo = Foo.init; (3) The current workaround when one wants (2) to construct the object at runtime is to define a static opCall, but that's messy and inconsistent. It's just one more quirk one needs to learn to effectively use the language.

Yup. It looks like a poorly copied syntactic carry-over from C++ that doesn't quite make sense in the presence of T.init. -- Dmitry Olshansky
Nov 29 2012
prev sibling next sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Thursday, November 29, 2012 20:27:32 Dmitry Olshansky wrote:
 11/29/2012 7:24 AM, Walter Bright пишет:
 On 11/29/2012 4:47 AM, monarch_dodra wrote:
 On Sunday, 25 November 2012 at 16:47:08 UTC, Dmitry Olshansky wrote:
 Thoughts?

I don't know about "killing" T(), but I think there *needs* to be an (easy) mechanism to declare ***and*** run-time initialize an object, in a single and comprehensive line. I had proposed something 2 months ago here: http://forum.dlang.org/thread/bvuquzwfykiytdwsqkky forum.dlang.org The proposal wasn't perfect, but still. We need to figure something out...

The original idea is that there should be *no such thing* as default construction of a struct as being anything other than T.init. The default construction of a struct should be a compile time creature, not a runtime one.

Okay let it be. I'm not against having a defined blank default constructed object. Just don't make T() mean it, please!

I'm all for T() meaning T.init if T doesn't have a static opCall, but T() shouldn't be guaranteed to be T.init. I'd very much like to see code like auto t = T(); to continue to work regardless of whether T has a static opCall or not. But it should be able to have a static opCall, and T() should work if it doesn't have one. Assuming that T() means T.init makes no sense. - Jonathan M Davis
Nov 29 2012
prev sibling next sibling parent "deadalnix" <deadalnix gmail.com> writes:
On Thursday, 29 November 2012 at 23:31:54 UTC, Jonathan M Davis 
wrote:
 I'm all for T() meaning T.init if T doesn't have a static 
 opCall, but T()
 shouldn't be guaranteed to be T.init. I'd very much like to see 
 code like

 auto t = T();

 to continue to work regardless of whether T has a static opCall 
 or not. But it
 should be able to have a static opCall, and T() should work if 
 it doesn't have
 one. Assuming that T() means T.init makes no sense.

opCall is really a poor workaround because it doesn't allow new, it is inconsistent with other constructors and is unexpected by the user. How much opCall has spread tells more about how parameterless constructor is needed than something else.
Nov 29 2012
prev sibling next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 11/29/2012 3:59 PM, Mehrdad wrote:
 On Thursday, 29 November 2012 at 03:24:40 UTC, Walter Bright wrote:
 The original idea is that there should be *no such thing* as default
 construction of a struct as being anything other than T.init. The
 default construction of a struct should be a compile time creature,
 not a runtime one.

Why?

So copying them is always an unsurprising bit copy.
Nov 29 2012
parent Walter Bright <newshound2 digitalmars.com> writes:
On 11/30/2012 2:21 PM, Walter Bright wrote:
 On 11/29/2012 3:59 PM, Mehrdad wrote:
 On Thursday, 29 November 2012 at 03:24:40 UTC, Walter Bright wrote:
 The original idea is that there should be *no such thing* as default
 construction of a struct as being anything other than T.init. The
 default construction of a struct should be a compile time creature,
 not a runtime one.

Why?

So copying them is always an unsurprising bit copy.

Eh, scratch that. It was so initialization is always an unsurprising bit copy. It means, for example, that the constructor for a struct never sees uninitialized fields.
Nov 29 2012
prev sibling parent Nick Treleaven <ntrel-public yahoo.co.uk> writes:
On 29/11/2012 12:10, Maxim Fomin wrote:
 On Thursday, 29 November 2012 at 10:41:46 UTC, Mehrdad wrote:
 I'm just not understanding the whole "the default construction of a
 struct should be a compile time creature, not a runtime one".



 Don't you have to initialize the struct with zero's either way?

 So either way, you're going to have to initialize it... so no perf
 increase in any way. Why prevent the user from default-initializing it
 the way he wants to?

Every type has a CT-known default initializer, even classes have (null). If structures had a runtime one, this would break code (especially templates and CTFE) which relies on knowing something about constant default instance of a type at CT. extern bool foo(); struct S { int i; this() { i = foo() ? 1 : -1; } } --------- S s; dosmth(s); --------- //somewhere in Phobos void dosmth(T) (T obj) { T val; // is i 0, -1 or 1 ? }

I think s.i and val.i should be zero. S.this() should never be called implicitly IMO, but instead like this: // runtime code S s = S(); These two should always be equivalent, and compile-time evaluated: S s; S s = S.init; If we actually need non-trivial compile-time 'default' constructors, they should have a different syntax: // runtime ctors this(); this(int x = 0); this(T...)(T args); // CT ctor (if actually needed) default this(){...} S.init would imply a call to the above CT constructor.
Nov 30 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Thursday, 29 November 2012 at 03:24:40 UTC, Walter Bright 
wrote:
 The original idea is that there should be *no such thing* as 
 default construction of a struct as being anything other than 
 T.init. The default construction of a struct should be a 
 compile time creature, not a runtime one.

Why?
Nov 28 2012
prev sibling next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Thursday, November 29, 2012 14:24:38 Walter Bright wrote:
 On 11/29/2012 4:47 AM, monarch_dodra wrote:
 On Sunday, 25 November 2012 at 16:47:08 UTC, Dmitry Olshansky wrote:
 Thoughts?

I don't know about "killing" T(), but I think there *needs* to be an (easy) mechanism to declare ***and*** run-time initialize an object, in a single and comprehensive line. I had proposed something 2 months ago here: http://forum.dlang.org/thread/bvuquzwfykiytdwsqkky forum.dlang.org The proposal wasn't perfect, but still. We need to figure something out...

The original idea is that there should be *no such thing* as default construction of a struct as being anything other than T.init. The default construction of a struct should be a compile time creature, not a runtime one. Any methods or workarounds to try and make T() produce something different from T.init is bad D practice. The compiler tries to statically head them off, but probably should do a better job of that.

I believe that it's very common practice in D to use static opCall to effectively give structs a default constructor when one is desired. I don't think that we need to try and add any default construction mechanism beyond that and that init is sufficient for most cases, but I don't see any reason why using static opCall for providing something closer to a default constructor would be a bad idea, and your post seems to indicate that you think that it is. - Jonathan M Davis
Nov 28 2012
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 11/29/2012 11:10 PM, Jonathan M Davis wrote:
  [...]

You're right, I had overlooked the point that having no default constructor means that the default construction will *always* succeed. This is a large simplification. Frankly, non-trivial default construction has always smelled like a bad practice to me, though it's not always obvious why.
Nov 29 2012
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 11/30/2012 3:31 PM, Mehrdad wrote:
 If that's the case, then we need to get rid of postblits entirely.

The only justification I've ever been able to come up with for postblits is implementing a reference counting type.
Nov 30 2012
next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Saturday, December 01, 2012 05:42:23 deadalnix wrote:
 On Saturday, 1 December 2012 at 04:32:44 UTC, Walter Bright wrote:
 On 11/30/2012 3:31 PM, Mehrdad wrote:
 If that's the case, then we need to get rid of postblits
 entirely.

The only justification I've ever been able to come up with for postblits is implementing a reference counting type.

Which have to check for null all over the place because it can be uninitialized.

That's only an issue with a ref-counting type which is attempting to be non- nullable. Most shared pointers are nullable, making such checks be required regardless. In most cases, I would consider this to be a complete non-issue. - Jonathan M Davis
Nov 30 2012
parent Walter Bright <newshound2 digitalmars.com> writes:
On 12/1/2012 6:26 PM, deadalnix wrote:
 The reference to the counter can be null too, not only the payload.

Or you can embed the counter in the type, and there is no reference to it.
Dec 03 2012
prev sibling next sibling parent "deadalnix" <deadalnix gmail.com> writes:
On Saturday, 1 December 2012 at 04:51:44 UTC, Jonathan M Davis 
wrote:
 On Saturday, December 01, 2012 05:42:23 deadalnix wrote:
 On Saturday, 1 December 2012 at 04:32:44 UTC, Walter Bright 
 wrote:
 On 11/30/2012 3:31 PM, Mehrdad wrote:
 If that's the case, then we need to get rid of postblits
 entirely.

The only justification I've ever been able to come up with for postblits is implementing a reference counting type.

Which have to check for null all over the place because it can be uninitialized.

That's only an issue with a ref-counting type which is attempting to be non- nullable. Most shared pointers are nullable, making such checks be required regardless. In most cases, I would consider this to be a complete non-issue. - Jonathan M Davis

The reference to the counter can be null too, not only the payload.
Nov 30 2012
prev sibling next sibling parent Dmitry Olshansky <dmitry.olsh gmail.com> writes:
12/1/2012 8:32 AM, Walter Bright пишет:
 On 11/30/2012 3:31 PM, Mehrdad wrote:
 If that's the case, then we need to get rid of postblits entirely.

The only justification I've ever been able to come up with for postblits is implementing a reference counting type.

+ copy on write + value semantics of typically small containers* *Small string optimization is only useful if the type has value semantics and lives on the stack. -- Dmitry Olshansky
Dec 01 2012
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 12/1/2012 3:43 PM, Jonathan M Davis wrote:
 Any struct which contains reference types needs them. For instance, a struct
 containing int[] needs to dup that array if it doesn't want to have the copy
 referring to the same elements as the original and therefore risk having them
 be mutated. Arrays with immutable elements don't have that problem, but those
 with mutable elements (and to some extent those with const) do have such a
 problem, as to structs with classes or structs which are reference types, etc.
 I'm surprised that you'd think that postblit constructors were only useful for
 implementing reference counting types. IMHO, the language would be crippled
 without a postblit or copy constructor for structs.

On the other hand, I'd say you have a pretty expensive design if it required you to allocate memory in order to do a copy. Making copies ought to be a cheap operation. It's not hard to implement copy-on-write for the referenced data. In fact, with reference counting, you shouldn't need to make those dups.
Dec 03 2012
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 12/8/2012 5:00 PM, Mehrdad wrote:
 Copying objects ought to be expensive.

I don't understand the rationale for that.
Dec 09 2012
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 12/9/2012 11:16 AM, Jonathan M Davis wrote:
 That's kind of the point of having posblits in the first place,

As I argued previously, the only justification I can think of for postblit is for reference counting. The counters posted here were variations on reference counting, or could be done in terms of reference counting. In fact, I think we could solve the postblit problems with const, immutable, and shared by dispensing with postblit entirely and providing some sort of compiler magic for doing ref counting.
Dec 09 2012
next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, December 09, 2012 13:41:46 Jonathan M Davis wrote:
 Declaring a struct which contains reference types and does a deep copy with
 postblit obviously incurs a performance cost, but it's up to the programmer
 who declared it to decide whether that's worth it or not. I see no reason
 for the language to try and disallow that. That's overly restrictive.

Not to mention, we've already discussed fixing current features so that they stop disallowing certain idioms. For instance, in order to make it so that caching and lazy loading and whatnot are not disallowed in classes due to issues with const, we've talked about removing toString, opEquals, toHash, and opCmp from Object. Then the programmer can decide whether they want to use const or not and we'd no longer be disallowing the idioms that are prevented by forcing const on Object. You're basically suggesting that we disallow any idiom which requires that structs be deep copied, and I think that that's bad policy. It's one thing to encourage programmers to not write such structs and to use other idioms like COW or reference counting. It's another thing entirely to disallow them. It's one of C++'s prime tenets to try and not force the programmer to program in a certain way or in a certain paradigm, and I think that D should do the same. If we want to encourage certain idioms and discourage others, fine. But outright disallowing them is a bad idea IMHO. - Jonathan M Davis
Dec 09 2012
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 12/9/2012 2:10 PM, Jonathan M Davis wrote:
 You're basically suggesting that we disallow any idiom which requires that
 structs be deep copied, and I think that that's bad policy. It's one thing to
 encourage programmers to not write such structs and to use other idioms like
 COW or reference counting. It's another thing entirely to disallow them. It's
 one of C++'s prime tenets to try and not force the programmer to program in a
 certain way or in a certain paradigm, and I think that D should do the same.

We already disallow several C++ idioms - like multiple inheritance, using a type as both a value and a reference type, and head const. We believe that these are bad design patterns, despite them being used often in C++. I do not dispute that deep copy is commonly used in C++. I challenge the idea that it is a good design pattern, i.e. better than using copy-on-write.
Dec 09 2012
next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Monday, December 10, 2012 05:12:17 Mehrdad wrote:
 On Monday, 10 December 2012 at 03:53:04 UTC, Walter Bright wrote:
 I do not dispute that deep copy is commonly used in C++. I
 challenge the idea that it is a good design pattern, i.e.
 better than using copy-on-write.

Ignoring the design issue, it's plain faster -- for COW you have to perform checks on every call.

Well, if you're doing a lot of copying and very little mutating, then COW could end up being much faster if copying is expensive (strings types in C++ would probably be a good example of this). However, if you're doing a lot of mutating, then COW doesn't save you much (if anything) and definitely could end up costing you more with all of the extra checks. It all depends on the use case. Regardless, I think that the decision should be made by the programmer. If we want to do things to encourage better idioms or make them easier, then fine, but I don't think that it makes sense to get rid of being able to deep copy structs via a postblit or copy constructor. If anything, I think that we need to _add_ copy constructors so that we can get around the impossibility of a const or immutable postblit. - Jonathan M Davis
Dec 09 2012
prev sibling next sibling parent Walter Bright <newshound2 digitalmars.com> writes:
On 12/9/2012 11:06 PM, deadalnix wrote:
 On Monday, 10 December 2012 at 03:53:04 UTC, Walter Bright wrote:
 On 12/9/2012 2:10 PM, Jonathan M Davis wrote:
 You're basically suggesting that we disallow any idiom which requires that
 structs be deep copied, and I think that that's bad policy. It's one thing to
 encourage programmers to not write such structs and to use other idioms like
 COW or reference counting. It's another thing entirely to disallow them. It's
 one of C++'s prime tenets to try and not force the programmer to program in a
 certain way or in a certain paradigm, and I think that D should do the same.

We already disallow several C++ idioms - like multiple inheritance, using a type as both a value and a reference type, and head const. We believe that these are bad design patterns, despite them being used often in C++.

D didn't removed them, it replaced it with better idioms, that still allow the valid uses and make bogus uses less likely.

A lot of people argued very strongly for head const.
 Here we are talking to remove a possibility, without providing something
better.

Dec 10 2012
prev sibling next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 12/9/2012 8:17 PM, Jonathan M Davis wrote:
 Not to mention, unless you can find ways to implement everything that you'd
 need a postblit or copy constructor to do without them and get rid of
 postblits, doing deep copies is going to be possible. And in the process of
 getting rid of postblits, you could easily end up disallowing other stuff which
 was useful and innocuous (e.g. something as simple as being able to print out
 when an object is copied when debugging).

Have you ever written a struct that requires a deep copy? I have, and I always wound up redoing it so the deep copy was unnecessary.
Dec 10 2012
next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 12/10/2012 10:40 AM, Dan wrote:
 For something as simple as this would you introduce COW?

Yes.
Dec 10 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 12/11/12 2:19 AM, monarch_dodra wrote:
 On Tuesday, 11 December 2012 at 03:09:53 UTC, Dan wrote:
 On Tuesday, 11 December 2012 at 01:47:38 UTC, Walter Bright wrote:
 On 12/10/2012 10:40 AM, Dan wrote:
 For something as simple as this would you introduce COW?

Yes.

Any D examples showing the pattern in action with structs? I did not see anything in the index of TDPL on COW except one reference regarding strings. If it is a better approach I would like to know more about how it is done and the trade-offs. Some of the responses refer to having to check on every access, so is that not tough to manage? Thanks Dan

Here is a example of using COW for a simple object that wraps nothing more than an int:

Walter and I discussed that it should be possible to automate the dupIfNeeded() (I call it ensureUnique()) calls like this: * If a method is const or immutable, leave as is * For all other methods, insert a ensureUnique() automatically in the prolog code For this to work, the state must be private and all primitives must be implemented via methods (as opposed to free functions). Andrei
Dec 11 2012
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 12/11/12 9:37 AM, Dan wrote:
 On Tuesday, 11 December 2012 at 13:01:44 UTC, Andrei Alexandrescu wrote:
 Walter and I discussed that it should be possible to automate the
 dupIfNeeded() (I call it ensureUnique()) calls like this:

 * If a method is const or immutable, leave as is
 * For all other methods, insert a ensureUnique() automatically in the
 prolog code

 For this to work, the state must be private and all primitives must be
 implemented via methods (as opposed to free functions).

This sounds good. But ensureUnique replacing dupIfNeeded really does two things - (1) determine if a copy is necessary (i.e. has a copy already been done) and (2) actually do the copy when necessary. What would it use to do the generic copy and what type of copy would it be (1 level deep (e.g. dup all fields of typeof(this)) or dup recursively deep)?

That would be left to the user in the form of a .dup primitive. Automation is only concerned with maintaining the refcount and making sure duplication is invoked when needed.
 I don't think the compiler can choose which of these two is desired by
 the user. This would then call for some form of field copy mechanism
 similar to postblit, likely defined by user.

 I think giving the struct designer a simpler way to do COW sounds very
 useful. Would love to read a DIP or notes on it. IMHO there are many
 coding use cases for me where even thinking about COW is premature
 optimization. For instance, when it comes to configuring a server at
 startup I really don't care too much about extra data copies.

Absolutely. On the other hand there's clearly a category of useful applications. I'll mull over a DIP. Andrei
Dec 11 2012
prev sibling parent Walter Bright <newshound2 digitalmars.com> writes:
On 12/10/2012 3:41 PM, Jonathan M Davis wrote:
 Yes. I've done it. And I don't think that I've ever bothered with COW. The
 extra complication isn't generally worth it as far as I'm concerned.

I don't believe it is any more complex than writing a postblit is.
Dec 10 2012
prev sibling parent Dmitry Olshansky <dmitry.olsh gmail.com> writes:
12/10/2012 8:12 AM, Mehrdad пишет:
 On Monday, 10 December 2012 at 03:53:04 UTC, Walter Bright wrote:
 I do not dispute that deep copy is commonly used in C++. I challenge
 the idea that it is a good design pattern, i.e. better than using
 copy-on-write.

Ignoring the design issue, it's plain faster -- for COW you have to perform checks on every call.

On every write obviously. In short - it depends on the use case. -- Dmitry Olshansky
Dec 10 2012
prev sibling next sibling parent "deadalnix" <deadalnix gmail.com> writes:
On Sunday, 9 December 2012 at 22:10:50 UTC, Jonathan M Davis
wrote:
 On Sunday, December 09, 2012 13:41:46 Jonathan M Davis wrote:
 Declaring a struct which contains reference types and does a 
 deep copy with
 postblit obviously incurs a performance cost, but it's up to 
 the programmer
 who declared it to decide whether that's worth it or not. I 
 see no reason
 for the language to try and disallow that. That's overly 
 restrictive.

Not to mention, we've already discussed fixing current features so that they stop disallowing certain idioms. For instance, in order to make it so that caching and lazy loading and whatnot are not disallowed in classes due to issues with const, we've talked about removing toString, opEquals, toHash, and opCmp from Object. Then the programmer can decide whether they want to use const or not and we'd no longer be disallowing the idioms that are prevented by forcing const on Object. You're basically suggesting that we disallow any idiom which requires that structs be deep copied, and I think that that's bad policy. It's one thing to encourage programmers to not write such structs and to use other idioms like COW or reference counting. It's another thing entirely to disallow them. It's one of C++'s prime tenets to try and not force the programmer to program in a certain way or in a certain paradigm, and I think that D should do the same. If we want to encourage certain idioms and discourage others, fine. But outright disallowing them is a bad idea IMHO. - Jonathan M Davis

I can't agree more
Dec 09 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 10 December 2012 at 03:53:04 UTC, Walter Bright wrote:
 I do not dispute that deep copy is commonly used in C++. I 
 challenge the idea that it is a good design pattern, i.e. 
 better than using copy-on-write.

Ignoring the design issue, it's plain faster -- for COW you have to perform checks on every call.
Dec 09 2012
prev sibling next sibling parent "deadalnix" <deadalnix gmail.com> writes:
On Monday, 10 December 2012 at 03:53:04 UTC, Walter Bright wrote:
 On 12/9/2012 2:10 PM, Jonathan M Davis wrote:
 You're basically suggesting that we disallow any idiom which 
 requires that
 structs be deep copied, and I think that that's bad policy. 
 It's one thing to
 encourage programmers to not write such structs and to use 
 other idioms like
 COW or reference counting. It's another thing entirely to 
 disallow them. It's
 one of C++'s prime tenets to try and not force the programmer 
 to program in a
 certain way or in a certain paradigm, and I think that D 
 should do the same.

We already disallow several C++ idioms - like multiple inheritance, using a type as both a value and a reference type, and head const. We believe that these are bad design patterns, despite them being used often in C++.

D didn't removed them, it replaced it with better idioms, that still allow the valid uses and make bogus uses less likely. Here we are talking to remove a possibility, without providing something better.
Dec 09 2012
prev sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 10 December 2012 at 16:46:19 UTC, Dmitry Olshansky 
wrote:
 12/10/2012 8:12 AM, Mehrdad пишет:
 On Monday, 10 December 2012 at 03:53:04 UTC, Walter Bright 
 wrote:
 I do not dispute that deep copy is commonly used in C++. I 
 challenge
 the idea that it is a good design pattern, i.e. better than 
 using
 copy-on-write.

Ignoring the design issue, it's plain faster -- for COW you have to perform checks on every call.

On every write obviously. In short - it depends on the use case.

lol I thought it was rather obvious that e.g. if you never do any calls to begin with then the point is moot... but thanks for the clarification lol =P
Dec 10 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Thursday, 29 November 2012 at 05:31:13 UTC, Jonathan M Davis 
wrote:
 <snip>

I'm just not understanding the whole "the default construction of a struct should be a compile time creature, not a runtime one". Don't you have to initialize the struct with zero's either way? So either way, you're going to have to initialize it... so no perf increase in any way. Why prevent the user from default-initializing it the way he wants to?
Nov 29 2012
prev sibling next sibling parent "Maxim Fomin" <maxim maxim-fomin.ru> writes:
On Thursday, 29 November 2012 at 10:41:46 UTC, Mehrdad wrote:
 I'm just not understanding the whole "the default construction 
 of a struct should be a compile time creature, not a runtime 
 one".



 Don't you have to initialize the struct with zero's either way?

 So either way, you're going to have to initialize it... so no 
 perf increase in any way. Why prevent the user from 
 default-initializing it the way he wants to?

Every type has a CT-known default initializer, even classes have (null). If structures had a runtime one, this would break code (especially templates and CTFE) which relies on knowing something about constant default instance of a type at CT. extern bool foo(); struct S { int i; this() { i = foo() ? 1 : -1; } } --------- S s; dosmth(s); --------- //somewhere in Phobos void dosmth(T) (T obj) { T val; // is i 0, -1 or 1 ? }
Nov 29 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Thursday, November 29, 2012 11:41:45 Mehrdad wrote:
 On Thursday, 29 November 2012 at 05:31:13 UTC, Jonathan M Davis
 
 wrote:
 <snip>

I'm just not understanding the whole "the default construction of a struct should be a compile time creature, not a runtime one". Don't you have to initialize the struct with zero's either way? So either way, you're going to have to initialize it... so no perf increase in any way. Why prevent the user from default-initializing it the way he wants to?

All init values must be known at compile time, and there are a number of places in the language where init needs to be used and default construction could never be used like it is in C++. Some limited default construction which could be done at compile time could be used in such cases, but it would have to be quite limited and would generally defeat the purpose of default construction in the first place. For instance, the possibility of exceptions being thrown totally screws with things, so default constructors would have to be nothrow. They'd probably have to be pure as well (and given that they'd have to be run at compile time, there wouldn't be any mutable static variables to access anyway, so the constructor would end up being effectively pure regarldess). And really, if you have to run them at compile time (which the language requires in order to do a number of the things that it does with init values), then default constructors don't buy you much of anything anyway. You'd never be able to really do more than just default initialize all of the member variables. You'd just be doing it in the default constructor instead of initalizing them directly. The main gain from having default constructors comes from being able to do stuff like RAII where just declaring the variable is enough (MFC's hourglass is a good example of that). It has to be able to do stuff at runtime to be of any real use, but all types need to be constructable at compile time, so that just doesn't work for a type which sits directly on the stack. At best, you'd end up with an init property and a default constructor, and depending on where the type was used, it would end being default-initialized with init or default constructed with the constructor, making the potential confusion and inconsistency great, and it would completely defeat the typical purpose of the default constructor of guaranteeing a particular default state. It all comes back to having to have a default state which must be known at compile time, since D uses it all over the place (default initializing member variables, default initializing elements in arrays, setting out parameters, etc.). That feature effectively kills default construction. You can have a no- args constructor via static opCall, but it's fundamentally different from a default constructor and really doesn't fill the same role at all. - Jonathan M Davis
Nov 29 2012
prev sibling next sibling parent "jerro" <a a.com> writes:
 The original idea is that there should be *no such thing* as 
 default construction of a struct as being anything other than 
 T.init. The default construction of a struct should be a 
 compile time creature, not a runtime one.

 Any methods or workarounds to try and make T() produce 
 something different from T.init is bad D practice. The compiler 
 tries to statically head them off, but probably should do a 
 better job of that.

The only reason to associate no parameter constructors with default values is because of how C++ works. There is no reason why Foo foo; (1) should be equivalent to auto foo = Foo(); (2) in D. We could allow constructors with no parameters and make (1) equivalent to auto foo = Foo.init; (3) The current workaround when one wants (2) to construct the object at runtime is to define a static opCall, but that's messy and inconsistent. It's just one more quirk one needs to learn to effectively use the language.
Nov 29 2012
prev sibling next sibling parent "deadalnix" <deadalnix gmail.com> writes:
On Thursday, 29 November 2012 at 12:10:06 UTC, Maxim Fomin wrote:
 On Thursday, 29 November 2012 at 10:41:46 UTC, Mehrdad wrote:
 I'm just not understanding the whole "the default construction 
 of a struct should be a compile time creature, not a runtime 
 one".



 Don't you have to initialize the struct with zero's either way?

 So either way, you're going to have to initialize it... so no 
 perf increase in any way. Why prevent the user from 
 default-initializing it the way he wants to?

Every type has a CT-known default initializer, even classes have (null). If structures had a runtime one, this would break code (especially templates and CTFE) which relies on knowing something about constant default instance of a type at CT. extern bool foo(); struct S { int i; this() { i = foo() ? 1 : -1; } } --------- S s; dosmth(s); --------- //somewhere in Phobos void dosmth(T) (T obj) { T val; // is i 0, -1 or 1 ? }

Error, S has no default initializer and must be explicitely initialized. What is complicated about that ?
Nov 29 2012
prev sibling next sibling parent "deadalnix" <deadalnix gmail.com> writes:
On Thursday, 29 November 2012 at 17:12:29 UTC, jerro wrote:
 The original idea is that there should be *no such thing* as 
 default construction of a struct as being anything other than 
 T.init. The default construction of a struct should be a 
 compile time creature, not a runtime one.

 Any methods or workarounds to try and make T() produce 
 something different from T.init is bad D practice. The 
 compiler tries to statically head them off, but probably 
 should do a better job of that.

The only reason to associate no parameter constructors with default values is because of how C++ works. There is no reason why Foo foo; (1) should be equivalent to auto foo = Foo(); (2) in D. We could allow constructors with no parameters and make (1) equivalent to auto foo = Foo.init; (3) The current workaround when one wants (2) to construct the object at runtime is to define a static opCall, but that's messy and inconsistent. It's just one more quirk one needs to learn to effectively use the language.

This do not solve problem with heap allocated struct being moved, but is definitively the right direction.
Nov 29 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Friday, 30 November 2012 at 03:21:24 UTC, Walter Bright wrote:
 So copying them is always an unsurprising bit copy.

Is this a speed thing, or is there a deeper reason? If it's b/c of performance, IMO the compiler should be taking care of figuring out such things internally, not forcing the programmer to write complicated code.
Nov 29 2012
prev sibling next sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Friday, November 30, 2012 14:27:56 Walter Bright wrote:
 On 11/29/2012 11:10 PM, Jonathan M Davis wrote:
 [...]

You're right, I had overlooked the point that having no default constructor means that the default construction will *always* succeed. This is a large simplification. Frankly, non-trivial default construction has always smelled like a bad practice to me, though it's not always obvious why.

You can easily have default construction which is relatively trivial which cannot be done in an init value. For instance, std.datetime.SysTime has TimeZone member, and because it's a class, it can't be initialized to anything other than null at compile time. It would be great if it could default to LocalTime, but that's just not possible with how init and construction works in D. On the other hand, if structs were default-constructed, it would be a non-issue, because the default constructor could initialize the TimeZone member. init buys us a _lot_, but there are definitely cases where having default construction at runtime would be very beneficial. Being able to construct structs with no-param constructors partially fixes that issue, but we can't completely fix it and will just have to work around the remaining use cases where the lack of default construction causes issues. - Jonathan M Davis
Nov 29 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Friday, 30 November 2012 at 03:27:57 UTC, Walter Bright wrote:
 Frankly, non-trivial default construction has always smelled 
 like a bad practice to me, though it's not always obvious why.

If that's the case, then we need to get rid of postblits entirely. They don't make sense if default-valued structs are meant to be bit-copyable. Consider: size_t n; struct S { this(this) { n++; } } void main() { auto s1 = S(); auto s2 = S.init; const s3 = const(S)(); immutable s4 = immutable(S)(); enum s5 = S(); auto t1 = s1; auto t2 = s2; auto t3 = s3; auto t4 = s4; auto t5 = s5; // What's 'n' supposed to be now, and why? }
Nov 29 2012
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Friday, 30 November 2012 at 03:27:57 UTC, Walter Bright wrote:
 On 11/29/2012 11:10 PM, Jonathan M Davis wrote:
 [...]

You're right, I had overlooked the point that having no default constructor means that the default construction will *always* succeed. This is a large simplification. Frankly, non-trivial default construction has always smelled like a bad practice to me, though it's not always obvious why.

Just so we're clear, what we are asking for (or at least, what I'm asking for) isn't even *default* construction, but a way of calling a constructor that takes no arguments. If: "auto a = T(5);" calls a constructor, then why can't: "auto a = T();" also call a constructor? I'll repeat that I think that D's T.init semantics, and move abilities, are a great tool, but the cost of for we are paying for it is un-necessarily high, and unwarranted. -------- The *only* reason I'd see against it, would be the initial confusion to C++ newcomers, but I mean: they already have to learn T.init and postblit anyways. They just have to learn it's a different language. Related: Why doesn't: "auto a = int(5);" work? and, even more importantly, why don't we have this? : "int* p = new int (5);" The fact that we have "T()/T(arg)", but not "int()/int(arg)" makes *zero* sense to me. THAT has been a source of initial confusion when I moved to D.
Nov 29 2012
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Friday, 30 November 2012 at 03:27:57 UTC, Walter Bright wrote:
 On 11/29/2012 11:10 PM, Jonathan M Davis wrote:
 [...]

You're right, I had overlooked the point that having no default constructor means that the default construction will *always* succeed. This is a large simplification. Frankly, non-trivial default construction has always smelled like a bad practice to me, though it's not always obvious why.

Just so we're clear, what we are asking for (or at least, what I'm asking for) isn't even *default* construction, but a way of calling a constructor that takes no arguments. If: "auto a = T(5);" calls a constructor, then why can't: "auto a = T();" also call a constructor? I'll repeat that I think that D's T.init semantics, and move abilities, are a great tool, but the cost of for we are paying for it is un-necessarily high, and unwarranted. -------- The *only* reason I'd see against it, would be the initial confusion to C++ newcomers, but I mean: they already have to learn T.init and postblit anyways. They just have to learn it's a different language. Related: Why doesn't: "auto a = int(5);" work? and, even more importantly, why don't we have this? : "int* p = new int (5);" The fact that we have "T()/T(arg)", but not "int()/int(arg)" makes *zero* sense to me. THAT has been a source of initial confusion when I moved to D.
Nov 29 2012
prev sibling next sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Friday, November 30, 2012 23:34:04 Dmitry Olshansky wrote:
 11/30/2012 3:31 AM, Jonathan M Davis пишет:
 I'm all for T() meaning T.init if T doesn't have a static opCall, but T()
 shouldn't be guaranteed to be T.init. I'd very much like to see code like
 
 auto t = T();
 
 to continue to work regardless of whether T has a static opCall or not.

And what you'd expect 't' to be then? And why such code is useful anyway? The only sane way I see is to make it an explicit call of 0-arg constructor then one can safely assume: 1. t is of type T 2. t is properly constructed and not some invalid state like T.init may be Currently with opCall it could be anything otherwise it ends up T.init or compiler error if T is a built-in type.

If auto t = T(); works then you don't have to care whether the type has a static opCall or not. You get the most valid default-constructed object that there is (or at least, the closest thing that there is to a default-constructed object). If that's init, then it's init. If it's static opCall, then it's static opCall. I don't want to have to care which it is. Also, if I see T t; I'm likely to think that was supposed to be initialized, but the programmer forgot, whereas with auto t = T(); it's clear that that it was intended to be initialized to whatever T() is (be it T.init or the result of a static opCall), and it's clear that the programmer didn't forget to initialize it.
 But it
 should be able to have a static opCall, and T() should work if it doesn't
 have one. Assuming that T() means T.init makes no sense.

Why in the nine hells we have static opCall to begin with? Probably to workaround 0-argument ctor situation. I haven't seen a better or at least sensible use case. Yet it has great potential for abuse. (and the fact that compiler picks opCall first (static or not) instead of constructor doesn't help matters much)

I don't know why we have static opCall, but it's used heavily for no-arg constructors, and it's extremely useful to be able to have those. Interestingly enough though, if you were to give all of your class static opCalls, it would become possible to construct classes as if they were structs and not care which you're dealing with (similar to what std.container.make tries to do). I don't know that that's necessarily a good idea, but it's at least another potentially useful way to use static opCall other than providing no-arg constructors to structs. - Jonathan M Davis
Nov 30 2012
prev sibling next sibling parent "deadalnix" <deadalnix gmail.com> writes:
On Saturday, 1 December 2012 at 04:32:44 UTC, Walter Bright wrote:
 On 11/30/2012 3:31 PM, Mehrdad wrote:
 If that's the case, then we need to get rid of postblits 
 entirely.

The only justification I've ever been able to come up with for postblits is implementing a reference counting type.

Which have to check for null all over the place because it can be uninitialized.
Nov 30 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Saturday, December 01, 2012 15:32:39 Walter Bright wrote:
 On 11/30/2012 3:31 PM, Mehrdad wrote:
 If that's the case, then we need to get rid of postblits entirely.

The only justification I've ever been able to come up with for postblits is implementing a reference counting type.

Any struct which contains reference types needs them. For instance, a struct containing int[] needs to dup that array if it doesn't want to have the copy referring to the same elements as the original and therefore risk having them be mutated. Arrays with immutable elements don't have that problem, but those with mutable elements (and to some extent those with const) do have such a problem, as to structs with classes or structs which are reference types, etc. I'm surprised that you'd think that postblit constructors were only useful for implementing reference counting types. IMHO, the language would be crippled without a postblit or copy constructor for structs. - Jonathan M Davis
Nov 30 2012
prev sibling next sibling parent "Rob T" <rob ucora.com> writes:
On Friday, 30 November 2012 at 07:20:39 UTC, monarch_dodra wrote:
 Related:
 Why doesn't:
 "auto a = int(5);"
 work? and, even more importantly, why don't we have this? :
 "int* p = new int (5);"
 The fact that we have "T()/T(arg)", but not "int()/int(arg)" 
 makes *zero* sense to me. THAT has been a source of initial 
 confusion when I moved to D.

I'd like to point out that what you are describing are examples of inconsistencies in D, and inconsistencies tend to be a significant cause of productivity loss in many areas. They needlessly complicate the learning curve, the users code, and even the documentation, and unless eliminated they will forever taint the user experience to a degree. As for the way structs are default constructed, I found it to be very difficult to comprehend. The documentation needs to be made better and more detailed. However, I've come around to think that the behaviors are correct and worth putting up with. Yes it can be very confusing at times, so if there's a way to make it less confusing, that would be a big help. --rt
Nov 30 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Saturday, December 01, 2012 08:26:54 deadalnix wrote:
 On Saturday, 1 December 2012 at 04:51:44 UTC, Jonathan M Davis
 
 wrote:
 On Saturday, December 01, 2012 05:42:23 deadalnix wrote:
 On Saturday, 1 December 2012 at 04:32:44 UTC, Walter Bright
 
 wrote:
 On 11/30/2012 3:31 PM, Mehrdad wrote:
 If that's the case, then we need to get rid of postblits
 entirely.

The only justification I've ever been able to come up with for postblits is implementing a reference counting type.

Which have to check for null all over the place because it can be uninitialized.

That's only an issue with a ref-counting type which is attempting to be non- nullable. Most shared pointers are nullable, making such checks be required regardless. In most cases, I would consider this to be a complete non-issue. - Jonathan M Davis

The reference to the counter can be null too, not only the payload.

Not a big deal, because you just have it so it would only be null if the payload is null, so you wouldn't need to check it much, if ever. At most, you'd have to check it when setting the payload to something other than null, and if the counter is set to null if the payload is set to null, then you don't even need to do that. - Jonathan M Davis
Nov 30 2012
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
Hum...

Hust wanted to add that "Appender" also suffers from this issue. 
It will, on _each_and_every_ append, check if its payload has 
been initialized :/

That's far from ideal behavior for a function that's supposed to 
squeeze as much performance as possible out of array 
manipulation...
Dec 05 2012
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
Hum...

Just wanted to add that "Appender" also suffers from this issue. 
It will, on _each_and_every_ append, check if its payload has 
been initialized :/

That's far from ideal behavior for a function that's supposed to 
squeeze as much performance as possible out of array 
manipulation...
Dec 05 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 3 December 2012 at 22:34:08 UTC, Walter Bright wrote:
 Making copies ought to be a cheap operation.

Beg to differ here. Copying pointers ought to be cheap. Copying objects ought to be expensive.
Dec 08 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, December 09, 2012 10:58:45 Walter Bright wrote:
 On 12/8/2012 5:00 PM, Mehrdad wrote:
 Copying objects ought to be expensive.

I don't understand the rationale for that.

I can see an argument that there's no requirement that copying objects be cheap, since there are plenty of objects where it's _not_ cheap to copy them and where it can't be cheap to copy them, but I don't see any argument for them _supposed_ to be being expensive to copy. I suspect that Merhdad just phrased it incorrectly. D and Phobos seem to be trying to take the tact that copying objects is supposed to be cheap and expect it to be cheap. But I honestly don't see how anyone can expect that to be relied upon. There are things that can be done to reduce the cost of copying an object (like COW), but l think that if you honestly expect that people aren't going to do stuff like this(this) { a = a.dup; } then you're deluding yourself. That's kind of the point of having posblits in the first place, and it's basically the example that TDPL gives as to what postblits do. And depending on the type of a's elements, that copy could be far worse than O(1). And even if it weren't, there's nothing stopping the programmer from doing other expensive operations in a postblit constructor (e.g. doing a deep copy of a red-black tree by recursively copying the whole thing). I'm fine with saying that various algorithms will be far more expensive if your struct defines an expensive postblit and that defining an expensive postblit is best avoided if possible, but there _will_ be plenty of expensive postblits in real world code. The only way that you can guarantee that copying structs is cheap is if you make it impossible to do a deep copy (i.e. get rid of postblit), which would be _far_ too limiting. Even stuff like ref-counted structs would become impossible in that case, because it's postblit which makes them possible as well. - Jonathan M Davis
Dec 09 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Sunday, 9 December 2012 at 19:17:06 UTC, Jonathan M Davis 
wrote:
 On Sunday, December 09, 2012 10:58:45 Walter Bright wrote:
 On 12/8/2012 5:00 PM, Mehrdad wrote:
 Copying objects ought to be expensive.

I don't understand the rationale for that.

I can see an argument that there's no requirement that copying objects be cheap, since there are plenty of objects where it's _not_ cheap to copy them and where it can't be cheap to copy them, but I don't see any argument for them _supposed_ to be being expensive to copy. I suspect that Merhdad just phrased it incorrectly.

+1 yup, sorry if it was confusing but Jon's clarification i what I meant.
Dec 09 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
To elaborate... what's the point of making everything cheap to 
copy?

Copying of an object is _not_ an operation like swap() or "move", 
which are essential to many algorithms.

Indeed, an object might not want to be copyable at all, or it 
might need to perform some expensive operation (as in Jon's 
example) in order for the copy to behave like a copy is supposed 
to.


The only thing that the arbitrary requirement "copies are 
supposed to be cheap" would do is that it would slow down 
everything else, forcing checks on operations that shouldn't need 
to be checked. And it's completely unnecessary unless you're 
working with reference types, in which case copying is already 
cheap anyways.


So basically, algorithms should _expect_ copying of arbitrary 
objects to be expensive, and there's no need for them to be 
otherwise. C++'s swap() illustrates the lack of the need for 
copying beautifully -- often times the only objects I copy 
"generically" in C++ are iterators. I never find the need to copy 
other objects... and I believe D has no such need, either.
Dec 09 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Sunday, 9 December 2012 at 20:02:15 UTC, Mehrdad wrote:
 I never find the need to copy other objects...

Correction: I _rarely_ find the need to copy other objects for _generic_ (er, templated) types (iterators being the exception I mentioned). But obviously copying is essential for many concrete classes, e.g. vector. And I have never had the need for copying a vector to be "cheap" in any sense... in fact, I feel it _should_ be expensive, because of what it's doing. So it works out perfectly.
Dec 09 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, December 09, 2012 21:02:14 Mehrdad wrote:
 So basically, algorithms should _expect_ copying of arbitrary
 objects to be expensive, and there's no need for them to be
 otherwise. C++'s swap() illustrates the lack of the need for
 copying beautifully -- often times the only objects I copy
 "generically" in C++ are iterators. I never find the need to copy
 other objects... and I believe D has no such need, either.

It's been a debate for some time whether Phobos should be able to rely on copying being cheap. Algorithms end up copying all the time, and it greatly simplifies things if you don't have to worry about copying being expensive. Being able to rely on copying being O(1) is something that Andrei has tried to push for, but I don't see how we really can given simple things like the need to dup arrays - though I can see arguing that it's up to the caller to make copying cheap if they want certain algorithms to be more efficient. The whole reason that the move* primitives were introduced was to try and combat expensive copying, but they complicate things a fair bit and rarely get used, even if they should be. D's move semantics also help, but ultimately, some things are just plain going to be expensive to copy, and I don't see how you can get around that without disallowing certain idioms, which would then cause a different set of problems. - Jonathan M Davis
Dec 09 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Sunday, 9 December 2012 at 20:14:18 UTC, Jonathan M Davis 
wrote:
 Algorithms end up copying all the time

Hmm... like which ones, and copying what kinds of objects?
Dec 09 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, December 09, 2012 21:21:10 Mehrdad wrote:
 On Sunday, 9 December 2012 at 20:14:18 UTC, Jonathan M Davis
 
 wrote:
 Algorithms end up copying all the time

Hmm... like which ones, and copying what kinds of objects?

Just calling front on a range is going to copy the element, and it's not uncommon for front to be called multiple times within a range-based function. Calls to front should probably minimized anyway, because front could be calculating front instead of simply returning it, but that's often not accounted for like it should be. But even if it _is_ accounted for, if front is being called in a loop (as it has to be while iterating), then it's going to be called over and over again. And if making a copy is O(n), then those calls to front end up being O(n) instead of O(1) like they're supposed to be, and calling in a loop becomes O(n * m) instead of O(n). And plenty of algorithms have to do further stuff on front, which may or may not result in further copies. The fact that copying can be worse than O(1) is actually pretty devastating to performance in general. But I don't see how it can be avoided other than advising people that it's a bad idea to declare types where copying them is worse than O(1) (which would mean that types where copying would be worse should try being COW types or reference types). Trying to write algorithms in a way which minimizes copying helps to be sure, but sometimes copying is necessary, and it's very easy to introduce unnecessary copying accidentally - copying whose cost would only become evident with types where copying is expensive, and most unit testing is done with primitive types or simple user- defined types. And most unit testing does not involve any sort of timing or benchmarking (which is part of why Andrei wants to push for std.benchmark), so the cost wouldn't even necessarily be evident if unit tests _did_ use types which were expensive to copy. So, I totally sympathize with Andrei and Walter wanting to say that copying must be O(1). I just don't think that it's realistic to expect that that's really going to be the case in the general case. At best, it's something that should be considered best practice. - Jonathan M Davis
Dec 09 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, December 09, 2012 12:36:09 Jonathan M Davis wrote:
 On Sunday, December 09, 2012 21:21:10 Mehrdad wrote:
 On Sunday, 9 December 2012 at 20:14:18 UTC, Jonathan M Davis
 
 wrote:
 Algorithms end up copying all the time

Hmm... like which ones, and copying what kinds of objects?

Just calling front on a range is going to copy the element...

Oh, and ranges themselves get copied all over the place, so if someone is foolish enough to make copying _them_ expensive, they're screwed. Of course, if someone actually made a range deep copy with a postblit, that would also violate the logic of how ranges even work, but it's an example of a type that gets copied a lot, and if copying it were expensive, then performance would probably tank. - Jonathan M Davis
Dec 09 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, December 09, 2012 13:11:43 Walter Bright wrote:
 On 12/9/2012 11:16 AM, Jonathan M Davis wrote:
 That's kind of the point of having posblits in the first place,

As I argued previously, the only justification I can think of for postblit is for reference counting. The counters posted here were variations on reference counting, or could be done in terms of reference counting. In fact, I think we could solve the postblit problems with const, immutable, and shared by dispensing with postblit entirely and providing some sort of compiler magic for doing ref counting.

It is incredibly common in C++ to have objects which live on the stack and are deep-copied when they're copied. postblit allows us to do the same in D (aside from the issues with it not working with const and immutable), and TDPL even gives examples of doing precisely that (e.g. duping an array in a postblit constructor). I don't see why we should disallow structs which are deep copied when they're copied. You're then forcing everything which contains any kind of reference types to be a reference type. And if you're going to do that, why not just make them all classes? Why allow complex structs like we have if you're just going to hamstring them like that? We could have just gone with C#'s limited structs if that's what you're looking for. Declaring a struct which contains reference types and does a deep copy with postblit obviously incurs a performance cost, but it's up to the programmer who declared it to decide whether that's worth it or not. I see no reason for the language to try and disallow that. That's overly restrictive. - Jonathan M Davis
Dec 09 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, December 09, 2012 19:52:15 Walter Bright wrote:
 We already disallow several C++ idioms - like multiple inheritance, using a
 type as both a value and a reference type, and head const. We believe that
 these are bad design patterns, despite them being used often in C++.

Well, I certainly dispute that deep copying with copy constructors or postblits is as bad as you seem to think that it is. It's often sub-optimal to be sure, but it's also often overkill to do something like COW given its extra level of complexity. I think that there's a world of difference between disallowing multiple inheritance and disallowing objects which are deep copied when they're copied.
 I do not dispute that deep copy is commonly used in C++. I challenge the
 idea that it is a good design pattern, i.e. better than using
 copy-on-write.

It's a easier to implement normal, deep copying than COW and a less error- prone. It's also less code. Particularly if you don't need the extra speed of COW, it actually seems better to me to simply do a deep copy with postblit. Why go to the extra effort of making COW work correctly if a simple, deep copy does the trick just fine? If you _do_ need the extra efficiency of COW, then you'll do it anyway. But plenty of folks won't need it and won't want to bother. Why force it on them? Not to mention, unless you can find ways to implement everything that you'd need a postblit or copy constructor to do without them and get rid of postblits, doing deep copies is going to be possible. And in the process of getting rid of postblits, you could easily end up disallowing other stuff which was useful and innocuous (e.g. something as simple as being able to print out when an object is copied when debugging). - Jonathan M Davis
Dec 09 2012
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Monday, 10 December 2012 at 11:03:28 UTC, Walter Bright wrote:
 On 12/9/2012 8:17 PM, Jonathan M Davis wrote:
 Not to mention, unless you can find ways to implement 
 everything that you'd
 need a postblit or copy constructor to do without them and get 
 rid of
 postblits, doing deep copies is going to be possible. And in 
 the process of
 getting rid of postblits, you could easily end up disallowing 
 other stuff which
 was useful and innocuous (e.g. something as simple as being 
 able to print out
 when an object is copied when debugging).

Have you ever written a struct that requires a deep copy?

The talk deviated to postblit, but in the original argument, the entire point of the thread is to have extra support for reference-type objects: Objects that are basically nothing more than a pointer. You'd have a hard time doing cheaper copy than that actually. Unfortunately, we have a problem regarding their initialization. This introduces subtle bugs, redundant "isInitialized" checks, ugly implementations etc...
Dec 10 2012
prev sibling next sibling parent "Dan" <dbdavidson yahoo.com> writes:
On Monday, 10 December 2012 at 11:03:28 UTC, Walter Bright wrote:
 Have you ever written a struct that requires a deep copy?

 I have, and I always wound up redoing it so the deep copy was 
 unnecessary.

How do you get around it? Let's say you have an address book structure that organizes addresses by email. Without a postblit or some deep copy, duplicating means sharing. When I see this, I think - I better add a postblit in case address books get copied. How would you approach it to not need deep copy? For something as simple as this would you introduce COW? Thanks, Dan --------------------------- module phone.phone; import std.stdio; struct Address { string street; string zipCode; string state; string country; } struct AddressBook { alias Address[string] EmailAddressMap; void addAddress(string email, Address address) { _emailAddressMap[email] = address; } private { EmailAddressMap _emailAddressMap; } } unittest { AddressBook office; office.addAddress("foo aol.com", Address("201 Foo Dr", "99999", "Fl", "USA")); AddressBook copy = office; copy.addAddress("goo aol.com", Address("202 Foo Dr", "99999", "Fl", "USA")); writeln(office); writeln(copy); }
Dec 10 2012
prev sibling next sibling parent "deadalnix" <deadalnix gmail.com> writes:
On Monday, 10 December 2012 at 16:52:54 UTC, monarch_dodra wrote:
 The talk deviated to postblit, but in the original argument, 
 the entire point of the thread is to have extra support for 
 reference-type objects: Objects that are basically nothing more 
 than a pointer. You'd have a hard time doing cheaper copy than 
 that actually.

The postblit discussion is very relevant here. The underlying point is the concept of ownership. When a structure contains a reference, it may own the data refered, or not. The existence of postblit allow for struct that own their data (by default in they don't). But the struct can't assert in a first place that it own the data. It may only hope that the user did things right in the first place and act as if it does own the data. In addition, the struct have to check all over the place that owned data does exists, because it has no way to ensure they do. The point that disallowing default constructor, and allowing postblit is inconsistent. One is made for a concept that is broken by the lack of the other. If it is really decided that ownership is a BAD THING©, then we should disable both postblit and default construction. COW isn't a valid replacement, because it imply different tradeoff. It is good for an object that is copied a lot and not modified often (or have an expensive modification). Ownership goes the other way around : it is good for an object that isn't copied often, but get a lot of cheap modifications.
Dec 10 2012
prev sibling next sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Monday, December 10, 2012 03:02:40 Walter Bright wrote:
 On 12/9/2012 8:17 PM, Jonathan M Davis wrote:
 Not to mention, unless you can find ways to implement everything that
 you'd
 need a postblit or copy constructor to do without them and get rid of
 postblits, doing deep copies is going to be possible. And in the process
 of
 getting rid of postblits, you could easily end up disallowing other stuff
 which was useful and innocuous (e.g. something as simple as being able to
 print out when an object is copied when debugging).

Have you ever written a struct that requires a deep copy? I have, and I always wound up redoing it so the deep copy was unnecessary.

Yes. I've done it. And I don't think that I've ever bothered with COW. The extra complication isn't generally worth it as far as I'm concerned. If I've really wanted something to be that cheap to copy around, I've usually just used a reference type and copied it explicitly when a deep copy was needed. I have no problem with people using COW if that's what they want to do, and I may even use it at some point, but I don't want to be forced to use it in order to have structs which hold reference types to be treated as value types. - Jonathan M Davis
Dec 10 2012
prev sibling next sibling parent "Dan" <dbdavidson yahoo.com> writes:
On Tuesday, 11 December 2012 at 01:47:38 UTC, Walter Bright wrote:
 On 12/10/2012 10:40 AM, Dan wrote:
 For something as simple as this would you introduce COW?

Yes.

Any D examples showing the pattern in action with structs? I did not see anything in the index of TDPL on COW except one reference regarding strings. If it is a better approach I would like to know more about how it is done and the trade-offs. Some of the responses refer to having to check on every access, so is that not tough to manage? Thanks Dan
Dec 10 2012
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Tuesday, 11 December 2012 at 03:09:53 UTC, Dan wrote:
 On Tuesday, 11 December 2012 at 01:47:38 UTC, Walter Bright 
 wrote:
 On 12/10/2012 10:40 AM, Dan wrote:
 For something as simple as this would you introduce COW?

Yes.

Any D examples showing the pattern in action with structs? I did not see anything in the index of TDPL on COW except one reference regarding strings. If it is a better approach I would like to know more about how it is done and the trade-offs. Some of the responses refer to having to check on every access, so is that not tough to manage? Thanks Dan

Here is a example of using COW for a simple object that wraps nothing more than an int: //---- struct S { static struct Payload { size_t count; int val; } Payload* payload; this(int i = 0) {payload = new Payload(1, i);} this(this) {++payload.count;} ~this() {--payload.count;} void opAssign(S other) { --payload.count; payload = other.payload; ++payload.count; } int get() {return payload.val;} void set(int i) { dupeIfNeeded(); payload.val = i; } void dupeIfNeeded() { if (payload.count > 1) { writeln("payload duplication"); --payload.count; payload = new Payload(1); } } } void main() { S s1 = 5; S s2 = s1; writeln("before set"); s2.set(4); writeln("after set"); } //---- The basic idea is that on every write, you check the payload count, and if it is not 1, then you duplicate the payload. This means you only pay for the copy when you *actually* need it. Drawbaks from this approach include: 1. Needs actual code. 2. NOT actually cheap: The counter goes up, and down, on each and every copy/destruction/pass: Requires a destructor and a postblit for RAII. 3 Implements deep ownership (*) *Imo, this is the worst part of COW for a language like D. In theory, you could just allocate your payload, and let the GC and default copy take care of everything else, and move on with your life. COW is (IMO) a c++ relic. Not only is this approach easy, but it is *also* efficient. If you *do* need actual object duplication, then you can implement "dup". //--------------------------------------- BTW: There is a subtle bug in this code. Can you spot it ;) ?
Dec 10 2012
prev sibling next sibling parent "Dan" <dbdavidson yahoo.com> writes:
On Tuesday, 11 December 2012 at 07:19:07 UTC, monarch_dodra wrote:
 Here is a example of using COW for a simple object that wraps
 nothing more than an int:

 This means you only pay for the copy when you *actually* need 
 it.

 Drawbaks from this approach include:
 1. Needs actual code.
 2. NOT actually cheap: The counter goes up, and down, on each 
 and
 every copy/destruction/pass: Requires a destructor and a 
 postblit
 for RAII.
 3 Implements deep ownership (*)

 *Imo, this is the worst part of COW for a language like D. In
 theory, you could just allocate your payload, and let the GC and
 default copy take care of everything else, and move on with your
 life. COW is (IMO) a c++ relic. Not only is this approach easy,
 but it is *also* efficient. If you *do* need actual object
 duplication, then you can implement "dup".

Thank you for the example. I see the benefit if performance is an issue ... but what happened to premature optimization? For example, the difference between the simple code below and a version with COW may be easy - but I don't think it is trivial. There is a fair amount of boilerplate bookeeping. What if the struct had two, three, or more maps: V1[K1], V2[K2], V3[K3]. Would you have three separate reference counter payloads, or just one payload with three maps? If you choose the latter you are still aggressively copying when unnecessary. It seems one could quickly take this to extremes. I did not understand the part about "In theory, you could just allocate your payload, and let the GC and default copy take care of everything else". --- struct AddressBook { alias Address[string] EmailAddressMap; this(this) { _emailAddressMap = _emailAddressMap.dup; } void addAddress(string email, Address address) { _emailAddressMap[email] = address; } private EmailAddressMap _emailAddressMap; } ---
 //---------------------------------------
 BTW: There is a subtle bug in this code. Can you spot it ;) ?

For me it did not compile (No constructor for payload), maybe you had a ctor for Payload? I think opAssign is not necessary - if I remove it it still seems to work since default opAssign calls postblit. I added the ctor, removed opAssign and put it here: http://dpaste.dzfl.pl/6ecfe675 Other than that - is there still a subtle bug? Thanks, Dan
Dec 11 2012
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Tuesday, 11 December 2012 at 12:52:03 UTC, Dan wrote:
 For me it did not compile (No constructor for payload), maybe 
 you had a ctor for Payload? I think opAssign is not necessary - 
 if I remove it it still seems to work since default opAssign 
 calls postblit. I added the ctor, removed opAssign and put it 
 here: http://dpaste.dzfl.pl/6ecfe675

 Other than that - is there still a subtle bug?

 Thanks,
 Dan

Strange, it had compiled for me this morning. Wonder what happened... Anyways, this just highlights one of D's inconsistencies: Payload a = Payload(1, 1); //Legal Payload* p1 = new Payload(1, 1); //Illegal??? Payload* p2 = [Payload(1, 1)].ptr; //Dirty workaround. Yeah... You can use the "dirty workaround": http://dpaste.dzfl.pl/c0258f66 Anyways, opAssign is *definitely* necessary. Without it, assignment would be equivalent to aliasing, and you wouldn't have actual COW (both instances would end up modified). What's more, if the current object had a handle on another payload, you wouldn't release it, causing it to duplicate for no reason (but that's moot considering COW is already broken). In my example, it works because no-one actually *calls* opAssign. The bug is this (on topic) //---- S s2 = S(2); S s0 = S(); s0 = s2; //Crap. //---- Here, you aren't actually calling "this(int = 0)". Nope. What you are actually calling is... nothing! This means s0 does not have an actual payload. This means that in theory, on every write, you need to check the count... and check there is actually a payload there first :D ! Yay extra useless checks and increased lazy initialization on every call!
 There is a fair amount of boilerplate bookeeping. What if the 
 struct had two, three, or more maps

Depends on the required granularity. I'd say ideally, you'd put all three maps in the same "Payload" struct, so things would not actually be more complicated. If you try to make each map individually COW, it would only be slightly more complex. You *would* take a further performance hit having to call "ensureUnique" on *each* map every modifying call. Furthermore, you may also take a performance hit in terms of locality of reference. But that'd be WAY premature to really worry about. What you have to keep in mind is that a "triple COW" design would only make sense if you have functions that modify one of your 3 maps. If they are always modified together, it'd make no sense to use "triple COW". //---- When everything is said and done, most of that code can be templated, or mixed-in.
Dec 11 2012
prev sibling next sibling parent "Dan" <dbdavidson yahoo.com> writes:
On Tuesday, 11 December 2012 at 13:22:42 UTC, monarch_dodra wrote:
 Anyways, opAssign is *definitely* necessary. Without it, 
 assignment would be equivalent to aliasing, and you wouldn't 
 have actual COW (both instances would end up modified). What's 
 more, if the current object had a handle on another payload, 
 you wouldn't release it, causing it to duplicate for no reason 
 (but that's moot considering COW is already broken). In my 
 example, it works because no-one actually *calls* opAssign.

Can you confirm this with an example? Again, I think default opAssign calls postblit. postblit increments. In this example everything looks fine without opAssign (except for the bug you point out regarding S()). http://dpaste.dzfl.pl/7fe03a43
 What you have to keep in mind is that a "triple COW" design 
 would only make sense if you have functions that modify one of 
 your 3 maps. If they are always modified together, it'd make no 
 sense to use "triple COW".

I see.
 //----
 When everything is said and done, most of that code can be 
 templated, or mixed-in.

If so, sounds like useful additions to phobos. Thanks Dan
Dec 11 2012
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Tuesday, 11 December 2012 at 13:39:22 UTC, Dan wrote:
 Can you confirm this with an example? Again, I think default 
 opAssign calls postblit. postblit increments. In this example 
 everything looks fine without opAssign (except for the bug you 
 point out regarding S()).

 http://dpaste.dzfl.pl/7fe03a43

Note that opAssign *also* calls the destructor. This is important too, or it would only get half the job done.
Dec 11 2012
prev sibling parent "Dan" <dbdavidson yahoo.com> writes:
On Tuesday, 11 December 2012 at 13:01:44 UTC, Andrei Alexandrescu 
wrote:
 Walter and I discussed that it should be possible to automate 
 the dupIfNeeded() (I call it ensureUnique()) calls like this:

 * If a method is const or immutable, leave as is
 * For all other methods, insert a ensureUnique() automatically 
 in the prolog code

 For this to work, the state must be private and all primitives 
 must be implemented via methods (as opposed to free functions).

This sounds good. But ensureUnique replacing dupIfNeeded really does two things - (1) determine if a copy is necessary (i.e. has a copy already been done) and (2) actually do the copy when necessary. What would it use to do the generic copy and what type of copy would it be (1 level deep (e.g. dup all fields of typeof(this)) or dup recursively deep)? I don't think the compiler can choose which of these two is desired by the user. This would then call for some form of field copy mechanism similar to postblit, likely defined by user. I think giving the struct designer a simpler way to do COW sounds very useful. Would love to read a DIP or notes on it. IMHO there are many coding use cases for me where even thinking about COW is premature optimization. For instance, when it comes to configuring a server at startup I really don't care too much about extra data copies. In reading this news groups I see things like Walter is not a fan of postblits and sees no need for them OR static named field initialization of structs is up for deprecation. These kind of issues give pause. I don't think 100% guarantees are needed - but it would be nice if discussions hinting at existing features being in or out of the language be addressed quickly and vocally by either Walter or you. Also, it would be nice if you release early and often when it comes to your thoughts and ideas on new features, like your thoughts on ensureUnique. Or, say, if you and Walter do have a potential solution or leaning when it comes to replacing postblits with true copy constructors it would be great to hear about. I'm not complaining - here, though. Keep up the good work. Thanks, Dan
Dec 11 2012