digitalmars.D - Time to kill T() as (sometimes) working T.init alias ?
- Dmitry Olshansky (65/65) Nov 25 2012 There was a topic on this NG before by deadalnix but apparently failed
- Timon Gehr (14/18) Nov 25 2012 I think it should be done, but then it is even less clear how .init
- Dmitry Olshansky (17/40) Nov 25 2012 I suppose that T.init is the blank object, that is composed of
- monarch_dodra (9/10) Nov 28 2012 I don't know about "killing" T(), but I think there *needs* to be
- Walter Bright (8/16) Nov 28 2012 The original idea is that there should be *no such thing* as default
- Mehrdad (3/7) Nov 28 2012 Why?
- Walter Bright (2/8) Nov 29 2012 So copying them is always an unsurprising bit copy.
- Walter Bright (5/15) Nov 29 2012 Eh, scratch that.
- Mehrdad (5/6) Nov 29 2012 Is this a speed thing, or is there a deeper reason?
- Jonathan M Davis (9/30) Nov 28 2012 I believe that it's very common practice in D to use static opCall to
- Mehrdad (8/9) Nov 29 2012 I'm just not understanding the whole "the default construction of
- Maxim Fomin (22/29) Nov 29 2012 Every type has a CT-known default initializer, even classes have
- deadalnix (4/37) Nov 29 2012 Error, S has no default initializer and must be explicitely
- Nick Treleaven (17/49) Nov 30 2012 I think s.i and val.i should be zero. S.this() should never be called
- Jonathan M Davis (34/49) Nov 29 2012 All init values must be known at compile time, and there are a number of...
- Walter Bright (6/7) Nov 29 2012 You're right, I had overlooked the point that having no default
- Jonathan M Davis (15/24) Nov 29 2012 You can easily have default construction which is relatively trivial whi...
- Mehrdad (21/23) Nov 29 2012 If that's the case, then we need to get rid of postblits entirely.
- Walter Bright (3/4) Nov 30 2012 The only justification I've ever been able to come up with for postblits...
- deadalnix (3/8) Nov 30 2012 Which have to check for null all over the place because it can be
- Jonathan M Davis (5/15) Nov 30 2012 That's only an issue with a ref-counting type which is attempting to be ...
- deadalnix (4/25) Nov 30 2012 The reference to the counter can be null too, not only the
- Jonathan M Davis (7/37) Nov 30 2012 Not a big deal, because you just have it so it would only be null if the...
- Walter Bright (2/3) Dec 03 2012 Or you can embed the counter in the type, and there is no reference to i...
- Jonathan M Davis (11/16) Nov 30 2012 Any struct which contains reference types needs them. For instance, a st...
- Walter Bright (6/15) Dec 03 2012 On the other hand, I'd say you have a pretty expensive design if it
- monarch_dodra (7/7) Dec 05 2012 Hum...
- monarch_dodra (7/7) Dec 05 2012 Hum...
- Mehrdad (4/5) Dec 08 2012 Beg to differ here.
- Walter Bright (2/3) Dec 09 2012 I don't understand the rationale for that.
- Jonathan M Davis (31/35) Dec 09 2012 I can see an argument that there's no requirement that copying objects b...
- Mehrdad (4/15) Dec 09 2012 +1 yup, sorry if it was confusing but Jon's clarification i what
- Mehrdad (20/20) Dec 09 2012 To elaborate... what's the point of making everything cheap to
- Mehrdad (8/9) Dec 09 2012 Correction:
- Jonathan M Davis (15/21) Dec 09 2012 It's been a debate for some time whether Phobos should be able to rely o...
- Mehrdad (3/4) Dec 09 2012 Hmm... like which ones, and copying what kinds of objects?
- Jonathan M Davis (30/36) Dec 09 2012 Just calling front on a range is going to copy the element, and it's not...
- Jonathan M Davis (8/17) Dec 09 2012 Oh, and ranges themselves get copied all over the place, so if someone i...
- Walter Bright (7/8) Dec 09 2012 As I argued previously, the only justification I can think of for postbl...
- Jonathan M Davis (16/26) Dec 09 2012 It is incredibly common in C++ to have objects which live on the stack a...
- Jonathan M Davis (17/21) Dec 09 2012 Not to mention, we've already discussed fixing current features so that ...
- deadalnix (3/41) Dec 09 2012 I can't agree more
- Walter Bright (6/12) Dec 09 2012 We already disallow several C++ idioms - like multiple inheritance, usin...
- Mehrdad (3/6) Dec 09 2012 Ignoring the design issue, it's plain faster -- for COW you have
- Jonathan M Davis (13/20) Dec 09 2012 Well, if you're doing a lot of copying and very little mutating, then CO...
- Dmitry Olshansky (4/10) Dec 10 2012 On every write obviously. In short - it depends on the use case.
- Mehrdad (5/21) Dec 10 2012 lol I thought it was rather obvious that e.g. if you never do any
- Jonathan M Davis (21/27) Dec 09 2012 Well, I certainly dispute that deep copying with copy constructors or
- Walter Bright (3/9) Dec 10 2012 Have you ever written a struct that requires a deep copy?
- monarch_dodra (9/22) Dec 10 2012 The talk deviated to postblit, but in the original argument, the
- deadalnix (22/27) Dec 10 2012 The postblit discussion is very relevant here.
- Dan (37/40) Dec 10 2012 How do you get around it? Let's say you have an address book
- Walter Bright (2/3) Dec 10 2012 Yes.
- Dan (9/12) Dec 10 2012 Any D examples showing the pattern in action with structs? I did
- monarch_dodra (67/81) Dec 10 2012 Here is a example of using COW for a simple object that wraps
- Dan (32/52) Dec 11 2012 Thank you for the example. I see the benefit if performance is an
- monarch_dodra (44/54) Dec 11 2012 Strange, it had compiled for me this morning. Wonder what
- Dan (10/24) Dec 11 2012 Can you confirm this with an example? Again, I think default
- monarch_dodra (4/9) Dec 11 2012 Oops. Correct.
- Andrei Alexandrescu (10/27) Dec 11 2012 [snip]
- Dan (33/40) Dec 11 2012 This sounds good. But ensureUnique replacing dupIfNeeded really
- Andrei Alexandrescu (7/31) Dec 11 2012 That would be left to the user in the form of a .dup primitive.
- Jonathan M Davis (9/22) Dec 10 2012 Yes. I've done it. And I don't think that I've ever bothered with COW. T...
- Walter Bright (2/4) Dec 10 2012 I don't believe it is any more complex than writing a postblit is.
- deadalnix (5/22) Dec 09 2012 D didn't removed them, it replaced it with better idioms, that
- Walter Bright (2/18) Dec 10 2012
- Dmitry Olshansky (7/11) Dec 01 2012 + copy on write
- monarch_dodra (25/32) Nov 29 2012 Just so we're clear, what we are asking for (or at least, what
- monarch_dodra (25/32) Nov 29 2012 Just so we're clear, what we are asking for (or at least, what
- Rob T (14/22) Nov 30 2012 I'd like to point out that what you are describing are examples
- Dmitry Olshansky (33/53) Nov 29 2012 Okay let it be. I'm not against having a defined blank default
- Nick Treleaven (12/50) Nov 29 2012 Totally agree with all this. I can't understand why T() should 'default
- Jonathan M Davis (8/32) Nov 29 2012 I'm all for T() meaning T.init if T doesn't have a static opCall, but T(...
- deadalnix (7/17) Nov 29 2012 opCall is really a poor workaround because it doesn't allow new,
- Dmitry Olshansky (15/47) Nov 30 2012 And what you'd expect 't' to be then? And why such code is useful
- Jonathan M Davis (24/50) Nov 30 2012 If
- Dmitry Olshansky (21/52) Dec 01 2012 Then I'd say
- Dmitry Olshansky (4/10) Dec 01 2012 A typo:
- jerro (12/20) Nov 29 2012 The only reason to associate no parameter constructors with
- Dmitry Olshansky (5/25) Nov 29 2012 Yup. It looks like a poorly copied syntactic carry-over from C++ that
- deadalnix (3/25) Nov 29 2012 This do not solve problem with heap allocated struct being moved,
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
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
11/25/2012 9:25 PM, Timon Gehr пишет:On 11/25/2012 05:47 PM, Dmitry Olshansky wrote: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.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.+1 I can't think of anything more logical then to do just that.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.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).... 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.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
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
On 11/29/2012 4:47 AM, monarch_dodra wrote:On Sunday, 25 November 2012 at 16:47:08 UTC, Dmitry Olshansky 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.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
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
On 11/29/2012 3:59 PM, Mehrdad wrote:On Thursday, 29 November 2012 at 03:24:40 UTC, Walter Bright wrote:So copying them is always an unsurprising bit copy.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 29 2012
On 11/30/2012 2:21 PM, Walter Bright wrote:On 11/29/2012 3:59 PM, Mehrdad wrote: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.On Thursday, 29 November 2012 at 03:24:40 UTC, Walter Bright wrote:So copying them is always an unsurprising bit copy.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 29 2012
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
On Thursday, November 29, 2012 14:24:38 Walter Bright wrote:On 11/29/2012 4:47 AM, monarch_dodra wrote: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 DavisOn Sunday, 25 November 2012 at 16:47:08 UTC, Dmitry Olshansky 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.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
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
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
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:Error, S has no default initializer and must be explicitely initialized. What is complicated about that ?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
On 29/11/2012 12:10, Maxim Fomin wrote:On Thursday, 29 November 2012 at 10:41:46 UTC, Mehrdad wrote: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.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 30 2012
On Thursday, November 29, 2012 11:41:45 Mehrdad wrote:On Thursday, 29 November 2012 at 05:31:13 UTC, Jonathan M Davis wrote: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<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
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
On Friday, November 30, 2012 14:27:56 Walter Bright wrote:On 11/29/2012 11:10 PM, Jonathan M Davis wrote: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[...]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
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
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
On Saturday, 1 December 2012 at 04:32:44 UTC, Walter Bright wrote:On 11/30/2012 3:31 PM, Mehrdad wrote:Which have to check for null all over the place because it can be uninitialized.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
On Saturday, December 01, 2012 05:42:23 deadalnix wrote:On Saturday, 1 December 2012 at 04:32:44 UTC, Walter Bright wrote: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 DavisOn 11/30/2012 3:31 PM, Mehrdad wrote:Which have to check for null all over the place because it can be uninitialized.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
On Saturday, 1 December 2012 at 04:51:44 UTC, Jonathan M Davis wrote:On Saturday, December 01, 2012 05:42:23 deadalnix wrote:The reference to the counter can be null too, not only the payload.On Saturday, 1 December 2012 at 04:32:44 UTC, Walter Bright wrote: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 DavisOn 11/30/2012 3:31 PM, Mehrdad wrote:Which have to check for null all over the place because it can be uninitialized.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
On Saturday, December 01, 2012 08:26:54 deadalnix wrote:On Saturday, 1 December 2012 at 04:51:44 UTC, Jonathan M Davis wrote: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 DavisOn Saturday, December 01, 2012 05:42:23 deadalnix wrote:The reference to the counter can be null too, not only the payload.On Saturday, 1 December 2012 at 04:32:44 UTC, Walter Bright wrote: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 DavisOn 11/30/2012 3:31 PM, Mehrdad wrote:Which have to check for null all over the place because it can be uninitialized.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
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
On Saturday, December 01, 2012 15:32:39 Walter Bright wrote:On 11/30/2012 3:31 PM, Mehrdad 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. - Jonathan M DavisIf 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
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
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
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
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
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
On Sunday, December 09, 2012 10:58:45 Walter Bright wrote:On 12/8/2012 5:00 PM, Mehrdad wrote: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 DavisCopying objects ought to be expensive.I don't understand the rationale for that.
Dec 09 2012
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:+1 yup, sorry if it was confusing but Jon's clarification i what I meant.On 12/8/2012 5:00 PM, Mehrdad wrote: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.Copying objects ought to be expensive.I don't understand the rationale for that.
Dec 09 2012
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
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
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
On Sunday, 9 December 2012 at 20:14:18 UTC, Jonathan M Davis wrote:Algorithms end up copying all the timeHmm... like which ones, and copying what kinds of objects?
Dec 09 2012
On Sunday, December 09, 2012 21:21:10 Mehrdad wrote:On Sunday, 9 December 2012 at 20:14:18 UTC, Jonathan M Davis wrote: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 DavisAlgorithms end up copying all the timeHmm... like which ones, and copying what kinds of objects?
Dec 09 2012
On Sunday, December 09, 2012 12:36:09 Jonathan M Davis wrote:On Sunday, December 09, 2012 21:21:10 Mehrdad wrote: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 DavisOn Sunday, 9 December 2012 at 20:14:18 UTC, Jonathan M Davis wrote:Just calling front on a range is going to copy the element...Algorithms end up copying all the timeHmm... like which ones, and copying what kinds of objects?
Dec 09 2012
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
On Sunday, December 09, 2012 13:11:43 Walter Bright wrote:On 12/9/2012 11:16 AM, Jonathan M Davis wrote: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 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 DavisThat'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
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
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:I can't agree moreDeclaring 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
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
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
On Monday, December 10, 2012 05:12:17 Mehrdad wrote:On Monday, 10 December 2012 at 03:53:04 UTC, Walter Bright wrote: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 DavisI 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
12/10/2012 8:12 AM, Mehrdad пишет:On Monday, 10 December 2012 at 03:53:04 UTC, Walter Bright wrote:On every write obviously. In short - it depends on the use case. -- Dmitry OlshanskyI 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 10 2012
On Monday, 10 December 2012 at 16:46:19 UTC, Dmitry Olshansky wrote:12/10/2012 8:12 AM, Mehrdad пишет: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 =POn Monday, 10 December 2012 at 03:53:04 UTC, Walter Bright wrote:On every write obviously. In short - it depends on the use case.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 10 2012
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
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
On Monday, 10 December 2012 at 11:03:28 UTC, Walter Bright wrote:On 12/9/2012 8:17 PM, Jonathan M Davis 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. Unfortunately, we have a problem regarding their initialization. This introduces subtle bugs, redundant "isInitialized" checks, ugly implementations etc...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?
Dec 10 2012
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
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
On 12/10/2012 10:40 AM, Dan wrote:For something as simple as this would you introduce COW?Yes.
Dec 10 2012
On Tuesday, 11 December 2012 at 01:47:38 UTC, Walter Bright wrote:On 12/10/2012 10:40 AM, Dan wrote: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 DanFor something as simple as this would you introduce COW?Yes.
Dec 10 2012
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: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 ;) ?On 12/10/2012 10:40 AM, Dan wrote: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 DanFor something as simple as this would you introduce COW?Yes.
Dec 10 2012
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:[snip]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
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, DanStrange, 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 mapsDepends 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
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/7fe03a43What 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
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/7fe03a43Oops. Correct. Note that opAssign *also* calls the destructor. This is important too, or it would only get half the job done.
Dec 11 2012
On 12/11/12 2:19 AM, monarch_dodra wrote:On Tuesday, 11 December 2012 at 03:09:53 UTC, Dan wrote:[snip] 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). AndreiOn Tuesday, 11 December 2012 at 01:47:38 UTC, Walter Bright wrote:Here is a example of using COW for a simple object that wraps nothing more than an int:On 12/10/2012 10:40 AM, Dan wrote: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 DanFor something as simple as this would you introduce COW?Yes.
Dec 11 2012
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
On 12/11/12 9:37 AM, Dan wrote:On Tuesday, 11 December 2012 at 13:01:44 UTC, Andrei Alexandrescu wrote: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.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.Absolutely. On the other hand there's clearly a category of useful applications. I'll mull over a DIP. Andrei
Dec 11 2012
On Monday, December 10, 2012 03:02:40 Walter Bright wrote:On 12/9/2012 8:17 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. 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 DavisNot 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
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
On Monday, 10 December 2012 at 03:53:04 UTC, Walter Bright wrote:On 12/9/2012 2:10 PM, Jonathan M Davis wrote: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.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++.
Dec 09 2012
On 12/9/2012 11:06 PM, deadalnix wrote:On Monday, 10 December 2012 at 03:53:04 UTC, Walter Bright wrote:A lot of people argued very strongly for head const.On 12/9/2012 2:10 PM, Jonathan M Davis wrote:D didn't removed them, it replaced it with better idioms, that still allow the valid uses and make bogus uses less likely.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++.Here we are talking to remove a possibility, without providing something better.
Dec 10 2012
12/1/2012 8:32 AM, Walter Bright пишет:On 11/30/2012 3:31 PM, Mehrdad wrote:+ 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 OlshanskyIf 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.
Dec 01 2012
On Friday, 30 November 2012 at 03:27:57 UTC, Walter Bright wrote:On 11/29/2012 11:10 PM, Jonathan M Davis wrote: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.[...]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
On Friday, 30 November 2012 at 03:27:57 UTC, Walter Bright wrote:On 11/29/2012 11:10 PM, Jonathan M Davis wrote: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.[...]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
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
11/29/2012 7:24 AM, Walter Bright пишет:On 11/29/2012 4:47 AM, monarch_dodra wrote:Okay let it be. I'm not against having a defined blank default 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?On Sunday, 25 November 2012 at 16:47:08 UTC, Dmitry Olshansky 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.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...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
On 29/11/2012 16:27, Dmitry Olshansky wrote:11/29/2012 7:24 AM, Walter Bright пишет: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.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! 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.
Nov 29 2012
On Thursday, November 29, 2012 20:27:32 Dmitry Olshansky wrote:11/29/2012 7:24 AM, Walter Bright пишет: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 DavisOn 11/29/2012 4:47 AM, monarch_dodra wrote:Okay let it be. I'm not against having a defined blank default constructed object. Just don't make T() mean it, please!On Sunday, 25 November 2012 at 16:47:08 UTC, Dmitry Olshansky 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.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 29 2012
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
11/30/2012 3:31 AM, Jonathan M Davis пишет:On Thursday, November 29, 2012 20:27:32 Dmitry Olshansky wrote: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.11/29/2012 7:24 AM, Walter Bright пишет: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.On 11/29/2012 4:47 AM, monarch_dodra wrote:Okay let it be. I'm not against having a defined blank default constructed object. Just don't make T() mean it, please!On Sunday, 25 November 2012 at 16:47:08 UTC, Dmitry Olshansky 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.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...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
On Friday, November 30, 2012 23:34:04 Dmitry Olshansky wrote:11/30/2012 3:31 AM, Jonathan M Davis пишет: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.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.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 DavisBut 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)
Nov 30 2012
12/1/2012 1:38 AM, Jonathan M Davis пишет:On Friday, November 30, 2012 23:34:04 Dmitry Olshansky wrote:Then I'd say 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.11/30/2012 3:31 AM, Jonathan M Davis пишет: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 withI'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.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
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.A typo: ... if none OF user-defined match. -- Dmitry Olshansky
Dec 01 2012
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
11/29/2012 9:12 PM, jerro пишет: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 OlshanskyThe 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
On Thursday, 29 November 2012 at 17:12:29 UTC, jerro wrote:This do not solve problem with heap allocated struct being moved, but is definitively the right direction.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