digitalmars.D - DIP 1018--The Copy Constructor--Community Review Round 1
- Mike Parker (16/16) Dec 18 2018 This is the feedback thread for the first round of Community
- Kagamin (18/18) Dec 19 2018 I think const postblit can be done by creating shadow copy of
- RazvanN (8/26) Dec 19 2018 This makes the blitting useless. Look at it this way: the
- Kagamin (33/34) Dec 20 2018 A copy constructor is very verbose though, imagine a copy
- RazvanN (5/39) Dec 20 2018 For that you can use metaprogramming:
- Neia Neutuladh (4/10) Dec 20 2018 this.tupleof = other.tupleof;
- Kagamin (3/5) Dec 21 2018 This will initialize field30 twice, won't it?
- Johannes Loher (2/7) Dec 22 2018 Indeed. What happens if field30 is const?
- 12345swordy (6/24) Dec 19 2018 You should not be resorting to calling the hidden symbol .__dtor,
- RazvanN (3/10) Dec 20 2018 I think that he was suggesting that that is compiler generated
- Boris-Barboris (54/56) Dec 21 2018 1). I do not like the ability to specify a mutable copy source.
- H. S. Teoh (7/47) Dec 21 2018 [...]
- Boris-Barboris (7/8) Dec 21 2018 Just in case: it's not my code, it's from the DIP example
- Nicholas Wilson (4/12) Dec 21 2018 One of the primary use cases for this is ref-counting stuff, the
- Boris-Barboris (4/8) Dec 22 2018 I thought ref-counted stuff is perfectly fine with postblits, was
- 12345swordy (6/15) Dec 22 2018 Nope, it turns out you can't implement ref-counting GC with
- RazvanN (6/22) Jan 05 2019 The motivation section, although it does not link to the RC GC,
- Rubn (19/27) Dec 21 2018 The DIP goes over why const wasn't used for the source. Consider
- Boris-Barboris (15/34) Dec 22 2018 Exactly my intent, to cause problems in this case and force a
- Rubn (7/46) Dec 22 2018 Blitting can't copy from one object type to another though. This
- Johannes Loher (29/73) Dec 22 2018 I totally agree. A copy modifying the source is very counter
- Walter Bright (5/6) Dec 22 2018 It means Named Return Value Optimization. Something I invented :-)
- Nicholas Wilson (6/28) Dec 22 2018 Modifying the source _is_ stupid, however const is transitive so
- RazvanN (30/86) Jan 05 2019 As it was stated above, the transitive nature of const and
- Boris-Barboris (28/48) Jan 05 2019 1). I personally do not want such feature, I find it dangerous.
- RazvanN (10/48) Jan 05 2019 I think that the discussion should transcend feelings and stick
- Boris-Barboris (14/23) Jan 05 2019 That feeling is coming from reason I highlighted in my original
- RazvanN (8/11) Jan 05 2019 Prior to this DIP, one might have defined:
- Boris-Barboris (7/12) Jan 05 2019 Oh... I didn't know C++ went that way, I now understand your
- RazvanN (2/15) Jan 05 2019 Yes.
- Paul Backus (29/55) Jan 05 2019 This seems like a weak justification to me. There are many things
- Johannes Loher (9/40) Dec 22 2018 In the section "Copy constructor call vs. blitting", the DIP
- RazvanN (9/12) Jan 05 2019 You are right, it's not very well stated, but I thought it would
- 12345swordy (6/22) Dec 22 2018 Motivation section needs to cover the RC GC algorithm and why the
- Atila Neves (5/21) Dec 22 2018 I only realised that there would be a change to move semantics
- RazvanN (8/35) Jan 05 2019 I think that move and copy constructors are orthogonal concepts.
- Atila Neves (15/51) Jan 09 2019 I didn't either until the example in the DIP:
- RazvanN (12/25) Jan 09 2019 C is not moved in this situation. The following code:
- Dru (26/26) Dec 22 2018 First I want to say that I hope it gets implemented
- RazvanN (47/73) Jan 05 2019 There is no default copy constructor. You define the copy
- Dru (6/15) Jan 05 2019 I offer a simpler logic:
- 12345swordy (4/7) Jan 06 2019 At the risk of sounding noobish, the copy constructor is only
- RazvanN (2/10) Jan 07 2019 Yes.
- Jacob Carlborg (4/8) Jan 07 2019 Why?
- H. S. Teoh (5/13) Jan 07 2019 https://forum.dlang.org/post/56378A55.20505@erdani.com
- Jacob Carlborg (4/5) Jan 09 2019 Would DIP1000 help with this?
This is the feedback thread for the first round of Community Review for DIP 1018, "The Copy Constructor": https://github.com/dlang/DIPs/blob/07da1f2cabc8b1bc3ad66034598a133e5ad13356/DIPs/DIP1018.md All review-related feedback on and discussion of the DIP should occur in this thread. The review period will end at 11:59 PM ET on January 4, or when I make a post declaring it complete. (This time I'm extending the review period by a few days because of the holidays.) At the end of Round 1, if further review is deemed necessary, the DIP will be scheduled for another round of community review. Otherwise, it will be queued for the Final Review and Formal Assessment by the language maintainers. Please familiarize yourself with the documentation for the Community Review before participating. https://github.com/dlang/DIPs/blob/master/PROCEDURE.md#community-review Thanks in advance to all who participate.
Dec 18 2018
I think const postblit can be done by creating shadow copy of instance fields (but still physically located in the field). struct A { int b; this(this) immutable { immutable int shadow_b; //reads go here //b++ immutable int t=shadow_b; shadow_b.__dtor(); b.__ctor(t+1); } } Type system would treat them as distinct instances, but would destroy shadow copy right before assignment, after which shadow copy's lifetime ends and it goes out of scope completely, which will also invalidate any retained pointers if any.
Dec 19 2018
On Wednesday, 19 December 2018 at 11:53:12 UTC, Kagamin wrote:I think const postblit can be done by creating shadow copy of instance fields (but still physically located in the field). struct A { int b; this(this) immutable { immutable int shadow_b; //reads go here //b++ immutable int t=shadow_b; shadow_b.__dtor(); b.__ctor(t+1); } } Type system would treat them as distinct instances, but would destroy shadow copy right before assignment, after which shadow copy's lifetime ends and it goes out of scope completely, which will also invalidate any retained pointers if any.This makes the blitting useless. Look at it this way: the compiler blitts the struct (in this case, `b`), then it makes another copy of it (`immutable int t = b) solely for the purpose of destroying the field for the former instance (!!!!!!). All this copies become useless when using the copy constructor. Cheers, RazvanN
Dec 19 2018
On Wednesday, 19 December 2018 at 13:40:41 UTC, RazvanN wrote:All this copies become useless when using the copy constructor.A copy constructor is very verbose though, imagine a copy constructor for a struct with 30 fields, where it copies 29 fields and sets the 30th to null. Postblit is observation of pattern that copy constructor usually changes only a small part of the struct. In this synthetic example copying wouldn't make much difference: for this code: --- struct A { int b; void inc() { const int shadow_b=b; //b++ const int t=shadow_b; b=t+1; } } A f(ref A a) { A b=a; b.inc(); return b; } --- `ldc -Os` generates this code for function f: --- movl (%rdi), %eax addl $1, %eax retq ---
Dec 20 2018
On Thursday, 20 December 2018 at 14:41:49 UTC, Kagamin wrote:On Wednesday, 19 December 2018 at 13:40:41 UTC, RazvanN wrote:For that you can use metaprogramming: static foreach (i, ref field; src.tupleof) this.tupleof[i] = field; this.field_of_choice = null;[...]A copy constructor is very verbose though, imagine a copy constructor for a struct with 30 fields, where it copies 29 fields and sets the 30th to null.Postblit is observation of pattern that copy constructor usually changes only a small part of the struct. In this synthetic example copying wouldn't make much difference: for this code: --- struct A { int b; void inc() { const int shadow_b=b; //b++ const int t=shadow_b; b=t+1; } } A f(ref A a) { A b=a; b.inc(); return b; } --- `ldc -Os` generates this code for function f: --- movl (%rdi), %eax addl $1, %eax retq ---
Dec 20 2018
On Thu, 20 Dec 2018 14:41:49 +0000, Kagamin wrote:On Wednesday, 19 December 2018 at 13:40:41 UTC, RazvanN wrote:this.tupleof = other.tupleof; this.field30 = null; Pretty succinct.All this copies become useless when using the copy constructor.A copy constructor is very verbose though, imagine a copy constructor for a struct with 30 fields, where it copies 29 fields and sets the 30th to null.
Dec 20 2018
On Thursday, 20 December 2018 at 16:53:29 UTC, Neia Neutuladh wrote:this.tupleof = other.tupleof; this.field30 = null;This will initialize field30 twice, won't it?
Dec 21 2018
On Friday, 21 December 2018 at 12:01:49 UTC, Kagamin wrote:On Thursday, 20 December 2018 at 16:53:29 UTC, Neia Neutuladh wrote:Indeed. What happens if field30 is const?this.tupleof = other.tupleof; this.field30 = null;This will initialize field30 twice, won't it?
Dec 22 2018
On Wednesday, 19 December 2018 at 11:53:12 UTC, Kagamin wrote:I think const postblit can be done by creating shadow copy of instance fields (but still physically located in the field). struct A { int b; this(this) immutable { immutable int shadow_b; //reads go here //b++ immutable int t=shadow_b; shadow_b.__dtor(); b.__ctor(t+1); } } Type system would treat them as distinct instances, but would destroy shadow copy right before assignment, after which shadow copy's lifetime ends and it goes out of scope completely, which will also invalidate any retained pointers if any.You should not be resorting to calling the hidden symbol .__dtor, .__ctor in your production code as that is just asking for trouble. Which it is not guarantee to grant the behavior that you desire. Alex
Dec 19 2018
On Wednesday, 19 December 2018 at 14:34:37 UTC, 12345swordy wrote:On Wednesday, 19 December 2018 at 11:53:12 UTC, Kagamin wrote:I think that he was suggesting that that is compiler generated code.[...]You should not be resorting to calling the hidden symbol .__dtor, .__ctor in your production code as that is just asking for trouble. Which it is not guarantee to grant the behavior that you desire. Alex
Dec 20 2018
On Tuesday, 18 December 2018 at 14:51:39 UTC, Mike Parker wrote:This is the feedback thread for the first round of Community Review for DIP 1018, "The Copy Constructor"1). I do not like the ability to specify a mutable copy source. Under no circumstance should the code like A a; A fun() { return a; // lowered to return tmp.copyCtor(a) } void main() { A b = fun(); // the return value of fun() is moved to the location of b } be allowed to modify the value of a. This is an absolute pain to read\debug, and I would instead like to see a mandatory const\immutable on the source reference. Copy operation should not modify the source, or it should not be called a copy. If we are talking about a typical smart pointer struct (Refcounted), copy still should not modify the source. It is D's transitive const\immutable that is a problem here and it must be explicitly casted away by the developer. Only const also mitigates the combinatorial mess caused by 4 combinations of mutable\immutable copy constructors, since immutable is implicitly converted to const. 2). "A declaration is a copy constructor declaration if it is a constructor declaration that takes only one non-default parameter by reference that is of the same type as typeof(this), followed by any number of default parameters..." If you need other parameters, you are not performing a copy. Copy constructor needs no additional parameters. If the semantics of your domain problem involve parametrized post-copy operations, the code should state that explicitly - by using specialized properly-named methods, that notify the reader about this particularity. 3). Section "Copy constructor usage", I don't understand the difference: void main() { A a; A b = a; // copy constructor gets called b = a; // assignment, not initialization } and void main() { A a; a = fun(); // NRVO - no copy constructor call A b; // b is initialized after this semicolon. // why is this one not an assignment, but initialization? // do we have the difference formally and consistently defined for structs? b = gun(); // NRVO cannot be performed, copy constructor is called }
Dec 21 2018
On Fri, Dec 21, 2018 at 02:43:50PM +0000, Boris-Barboris via Digitalmars-d wrote:On Tuesday, 18 December 2018 at 14:51:39 UTC, Mike Parker wrote:Shouldn't const be inout in this case?This is the feedback thread for the first round of Community Review for DIP 1018, "The Copy Constructor"1). I do not like the ability to specify a mutable copy source. Under no circumstance should the code like A a; A fun() { return a; // lowered to return tmp.copyCtor(a) } void main() { A b = fun(); // the return value of fun() is moved to the location of b } be allowed to modify the value of a. This is an absolute pain to read\debug, and I would instead like to see a mandatory const\immutable on the source reference. Copy operation should not modify the source, or it should not be called a copy. If we are talking about a typical smart pointer struct (Refcounted), copy still should not modify the source. It is D's transitive const\immutable that is a problem here and it must be explicitly casted away by the developer. Only const also mitigates the combinatorial mess caused by 4 combinations of mutable\immutable copy constructors, since immutable is implicitly converted to const.2). "A declaration is a copy constructor declaration if it is a constructor declaration that takes only one non-default parameter by reference that is of the same type as typeof(this), followed by any number of default parameters..." If you need other parameters, you are not performing a copy. Copy constructor needs no additional parameters. If the semantics of your domain problem involve parametrized post-copy operations, the code should state that explicitly - by using specialized properly-named methods, that notify the reader about this particularity.[...] +1. Doing otherwise adds needless complexity for something I can't think of any use cases for. -- Programming is not just an act of telling a computer what to do: it is also an act of telling other programmers what you wished the computer to do. Both are important, and the latter deserves care. -- Andrew Morton
Dec 21 2018
On Friday, 21 December 2018 at 21:40:23 UTC, H. S. Teoh wrote:Shouldn't const be inout in this case?Just in case: it's not my code, it's from the DIP example snippets. I was advocating for forbidden mutable copy source. Inout indeed solves the boilerplate/combinatorial problem, but my main conjecture was about readability, wich is harmed by mutable source default.
Dec 21 2018
On Saturday, 22 December 2018 at 00:08:51 UTC, Boris-Barboris wrote:On Friday, 21 December 2018 at 21:40:23 UTC, H. S. Teoh wrote:One of the primary use cases for this is ref-counting stuff, the refcount needs to be mutable.Shouldn't const be inout in this case?Just in case: it's not my code, it's from the DIP example snippets. I was advocating for forbidden mutable copy source. Inout indeed solves the boilerplate/combinatorial problem, but my main conjecture was about readability, wich is harmed by mutable source default.
Dec 21 2018
On Saturday, 22 December 2018 at 02:02:55 UTC, Nicholas Wilson wrote:On Saturday, 22 December 2018 at 00:08:51 UTC, Boris-Barboris wrote: One of the primary use cases for this is ref-counting stuff, the refcount needs to be mutable.I thought ref-counted stuff is perfectly fine with postblits, was I wrong?
Dec 22 2018
On Saturday, 22 December 2018 at 09:07:52 UTC, Boris-Barboris wrote:On Saturday, 22 December 2018 at 02:02:55 UTC, Nicholas Wilson wrote:Nope, it turns out you can't implement ref-counting GC with postbilts. Andrei have tried to do this already and failed. That why they are introducing it, (and why it should be covered in the motivation section in the DIP).On Saturday, 22 December 2018 at 00:08:51 UTC, Boris-Barboris wrote: One of the primary use cases for this is ref-counting stuff, the refcount needs to be mutable.I thought ref-counted stuff is perfectly fine with postblits, was I wrong?
Dec 22 2018
On Saturday, 22 December 2018 at 14:43:46 UTC, 12345swordy wrote:On Saturday, 22 December 2018 at 09:07:52 UTC, Boris-Barboris wrote:The motivation section, although it does not link to the RC GC, it describes the problems encountered when trying to implement it. The main problem was the fact that when dealing with immutable fields, after the blitting phase is done it is not obvious in what state the immutable fields is (raw/cooked?).On Saturday, 22 December 2018 at 02:02:55 UTC, Nicholas Wilson wrote:Nope, it turns out you can't implement ref-counting GC with postbilts. Andrei have tried to do this already and failed. That why they are introducing it, (and why it should be covered in the motivation section in the DIP).On Saturday, 22 December 2018 at 00:08:51 UTC, Boris-Barboris wrote: One of the primary use cases for this is ref-counting stuff, the refcount needs to be mutable.I thought ref-counted stuff is perfectly fine with postblits, was I wrong?
Jan 05 2019
On Saturday, 22 December 2018 at 00:08:51 UTC, Boris-Barboris wrote:On Friday, 21 December 2018 at 21:40:23 UTC, H. S. Teoh wrote:The DIP goes over why const wasn't used for the source. Consider the following: struct A { int* ptr; } Now to simulate the copy constructor: A copy(ref const A a) { A result; result.ptr = a.ptr; // error can't convert `const int*` to `int*` return result; } Const is infectious and just causes more problems making it pretty much useless in the general case. It's only suitable for a very narrow use case. A lot of people using D avoid const entirely, except for really basic simple things involving primitive types.Shouldn't const be inout in this case?Just in case: it's not my code, it's from the DIP example snippets. I was advocating for forbidden mutable copy source. Inout indeed solves the boilerplate/combinatorial problem, but my main conjecture was about readability, wich is harmed by mutable source default.
Dec 21 2018
On Saturday, 22 December 2018 at 03:37:22 UTC, Rubn wrote:On Saturday, 22 December 2018 at 00:08:51 UTC, Boris-Barboris wrote: The DIP goes over why const wasn't used for the source. Consider the following: struct A { int* ptr; } Now to simulate the copy constructor: A copy(ref const A a) { A result; result.ptr = a.ptr; // error can't convert `const int*` to `int*` return result; } Const is infectious and just causes more problems making it pretty much useless in the general case. It's only suitable for a very narrow use case. A lot of people using D avoid const entirely, except for really basic simple things involving primitive types.Exactly my intent, to cause problems in this case and force a cast. For example, your code is actually not performing a copy of A. Your copy constructor is making a shallow copy, by duplicating only one vertex of the memory graph. If you were actually copying it, you would allocate a new int on the heap and initializa it from the const source.ptr without a problem. Current blitting is perfect for shallow copies as it is. If we are to give default semantical meaning to the copy constructor, that the programmer can generally rely on in the unfamiliar codebase, we need to restrict the developer. If we are to give it a semantic meaning of the function where you do whatever you want with source and dest, why should we call it a COPY constructor?
Dec 22 2018
On Saturday, 22 December 2018 at 09:01:09 UTC, Boris-Barboris wrote:On Saturday, 22 December 2018 at 03:37:22 UTC, Rubn wrote:Blitting can't copy from one object type to another though. This constructor will be able to do that.On Saturday, 22 December 2018 at 00:08:51 UTC, Boris-Barboris wrote: The DIP goes over why const wasn't used for the source. Consider the following: struct A { int* ptr; } Now to simulate the copy constructor: A copy(ref const A a) { A result; result.ptr = a.ptr; // error can't convert `const int*` to `int*` return result; } Const is infectious and just causes more problems making it pretty much useless in the general case. It's only suitable for a very narrow use case. A lot of people using D avoid const entirely, except for really basic simple things involving primitive types.Exactly my intent, to cause problems in this case and force a cast. For example, your code is actually not performing a copy of A. Your copy constructor is making a shallow copy, by duplicating only one vertex of the memory graph. If you were actually copying it, you would allocate a new int on the heap and initializa it from the const source.ptr without a problem. Current blitting is perfect for shallow copies as it is.If we are to give default semantical meaning to the copy constructor, that the programmer can generally rely on in the unfamiliar codebase, we need to restrict the developer. If we are to give it a semantic meaning of the function where you do whatever you want with source and dest, why should we call it a COPY constructor?Sure it's not a very accurate name, changing the name to be something else is fine. Just don't change the implementation to require const and make it that much harder to use.
Dec 22 2018
On Friday, 21 December 2018 at 14:43:50 UTC, Boris-Barboris wrote:On Tuesday, 18 December 2018 at 14:51:39 UTC, Mike Parker wrote: 1). I do not like the ability to specify a mutable copy source. Under no circumstance should the code like A a; A fun() { return a; // lowered to return tmp.copyCtor(a) } void main() { A b = fun(); // the return value of fun() is moved to the location of b } be allowed to modify the value of a.I totally agree. A copy modifying the source is very counter intuitive.2). "A declaration is a copy constructor declaration if it is a constructor declaration that takes only one non-default parameter by reference that is of the same type as typeof(this), followed by any number of default parameters..." If you need other parameters, you are not performing a copy. Copy constructor needs no additional parameters. If the semantics of your domain problem involve parametrized post-copy operations, the code should state that explicitly - by using specialized properly-named methods, that notify the reader about this particularity.I also agree with this. It is not even possible to pass something to the additional (default) parameters if not calling the Copy constructor explicitly, is it? ``` struct A { this(ref A src, int b = 0) {} } void main() { A a; auto b = a; // How to pass something as b? auto c = A(a, 1); // Works in this case } ```3). Section "Copy constructor usage", I don't understand the difference: void main() { A a; A b = a; // copy constructor gets called b = a; // assignment, not initialization } and void main() { A a; a = fun(); // NRVO - no copy constructor call A b; // b is initialized after this semicolon. // why is this one not an assignment, but initialization? // do we have the difference formally and consistently defined for structs? b = gun(); // NRVO cannot be performed, copy constructor is called }I believe the first case is straight forward, so your confusion probably stems from the second case: In the main function, we do have assignments to a and b. Those are not initializations and so no copy construction is taking place because of this. But when functions return, if NRVO (named return value optimization) cannot be performed, a copy of their return value is created. This happens in the case of gun because a is a module level variable and so NRVO cannot be performed. I believe this example is indeed a bit confusing, mostly because the DIP does not at all explain what actually happens here and only uses the cryptic acronym NRVO. I believe a more detailed description is needed here, maybe in conjunction with a comparison to how this example would work with postblit.
Dec 22 2018
On 12/22/2018 12:24 AM, Johannes Loher wrote:the cryptic acronym NRVO.It means Named Return Value Optimization. Something I invented :-) https://github.com/dlang/dmd/pull/9117 I shoulda patented it. Then I'd own all the other C++ compilers! HAHHAHHAHAHAHAHAHAAAA!
Dec 22 2018
On Saturday, 22 December 2018 at 08:24:15 UTC, Johannes Loher wrote:On Friday, 21 December 2018 at 14:43:50 UTC, Boris-Barboris wrote:Modifying the source _is_ stupid, however const is transitive so we have no way distinguish modification of the source from modifications through indirections in the source which is useful e.g. incrementing the reference count.On Tuesday, 18 December 2018 at 14:51:39 UTC, Mike Parker wrote: 1). I do not like the ability to specify a mutable copy source. Under no circumstance should the code like A a; A fun() { return a; // lowered to return tmp.copyCtor(a) } void main() { A b = fun(); // the return value of fun() is moved to the location of b } be allowed to modify the value of a.I totally agree. A copy modifying the source is very counter intuitive.
Dec 22 2018
On Friday, 21 December 2018 at 14:43:50 UTC, Boris-Barboris wrote:On Tuesday, 18 December 2018 at 14:51:39 UTC, Mike Parker wrote:As it was stated above, the transitive nature of const and immutable makes it impossible to safely copy objects with indirections. If you do not want to modify the source, then don't modify it from the copy constructor. This can be seen as a feature, not a bug.This is the feedback thread for the first round of Community Review for DIP 1018, "The Copy Constructor"1). I do not like the ability to specify a mutable copy source. Under no circumstance should the code like A a; A fun() { return a; // lowered to return tmp.copyCtor(a) } void main() { A b = fun(); // the return value of fun() is moved to the location of b } be allowed to modify the value of a. This is an absolute pain to read\debug, and I would instead like to see a mandatory const\immutable on the source reference. Copy operation should not modify the source, or it should not be called a copy. If we are talking about a typical smart pointer struct (Refcounted), copy still should not modify the source. It is D's transitive const\immutable that is a problem here and it must be explicitly casted away by the developer. Only const also mitigates the combinatorial mess caused by 4 combinations of mutable\immutable copy constructors, since immutable is implicitly converted to const.2). "A declaration is a copy constructor declaration if it is a constructor declaration that takes only one non-default parameter by reference that is of the same type as typeof(this), followed by any number of default parameters..." If you need other parameters, you are not performing a copy. Copy constructor needs no additional parameters. If the semantics of your domain problem involve parametrized post-copy operations, the code should state that explicitly - by using specialized properly-named methods, that notify the reader about this particularity.Agree, that was my initial design too, but in C++ you can define any number of default parameters and people coming from that background might have some cases where it is useful for them to have default parameters. Since this is the first iteration of a copy constructor implementation, we should try to stick as much as possible to the C++ design. Of course, in the case of const source we cannot do that since const means something else in D.3). Section "Copy constructor usage", I don't understand the difference: void main() { A a; A b = a; // copy constructor gets called b = a; // assignment, not initialization }A b = a is an initialization, therefore the copy constructor gets called. b = a is an assignemnt => opAssign gets calledand void main() { A a; a = fun(); // NRVO - no copy constructor callfun() returns by value so a copy constructor call should be performed when returning, but in this case, because named return value optimization may be applied, it will elide the copy.A b; // b is initialized after this semicolon. // why is this one not an assignment, but initialization? // do we have the difference formally and consistently defined for structs? b = gun(); // NRVO cannot be performed, copy constructor is called }Indeed, these examples are a bit confusing, because the copy constructor is called when returning, not on caller site. Even if you would have `A b = gun()`, the value of gun() would be moved, but a copy constructor may be called when returning from gun.
Jan 05 2019
On Saturday, 5 January 2019 at 13:34:09 UTC, RazvanN wrote:On Friday, 21 December 2018 at 14:43:50 UTC, Boris-Barboris wrote: As it was stated above, the transitive nature of const and immutable makes it impossible to safely copy objects with indirections. If you do not want to modify the source, then don't modify it from the copy constructor. This can be seen as a feature, not a bug.1). I personally do not want such feature, I find it dangerous. 2). That would make sense, if that was ever an ambiguous choice. I cannot think of any case where you need to modify the source outside of move operation, and that has nothing to do with copy constructors. You are giving a choice in the situation where only one, known answer is always right. We even know what's the problem is - our wonderful transitive const, that proves itself inadequate for typical smart pointer copying. Maybe instead of hacking in another poorly-integrated feature, we should fix that first? There were plenty of topics dedicated to head const semantics, why not address them now? It will solve lots of other range-related problems as well. 3). As someone who would like to stick to C++, I think you would say that you would feel right at home with the following semantics: this(ref const typeof(this) rhs) - copy constructor this(ref typeof(this) rhs) - move constructor I'm not a fan of the proposed opMove.Agree, that was my initial design too, but in C++ you can define any number of default parameters and people coming from that background might have some cases where it is useful for them to have default parameters. Since this is the first iteration of a copy constructor implementation, we should try to stick as much as possible to the C++ design. Of course, in the case of const source we cannot do that since const means something else in D.The key difference between copy\move constructors and other constructors is that they are implicitly inserted by the compiler in certain scenarios. Documentation that describes such constructors should not be littered with alternative exotic semantics that have nothing to do with implicitly inserted function calls. this(ref typeof(this) rhs, int b) is just a constructor and should not be mentioned anywhere in the copy constructor documentation, nor in the DIP.
Jan 05 2019
On Saturday, 5 January 2019 at 14:03:34 UTC, Boris-Barboris wrote:1). I personally do not want such feature, I find it dangerous.I think that the discussion should transcend feelings and stick to what can be done.2). That would make sense, if that was ever an ambiguous choice. I cannot think of any case where you need to modify the source outside of move operation, and that has nothing to do with copy constructors. You are giving a choice in the situation where only one, known answer is always right. We even know what's the problem is - our wonderful transitive const, that proves itself inadequate for typical smart pointer copying. Maybe instead of hacking in another poorly-integrated feature, we should fix that first? There were plenty of topics dedicated to head const semantics, why not address them now? It will solve lots of other range-related problems as well.I think that discussing whether const should be transitive or not is beyond the scope of this DIP.3). As someone who would like to stick to C++, I think you would say that you would feel right at home with the following semantics: this(ref const typeof(this) rhs) - copy constructor this(ref typeof(this) rhs) - move constructor I'm not a fan of the proposed opMove.But the opMove DIP proposes a different syntax: https://github.com/dlang/DIPs/pull/109/files#diff-50c61cb5afd3ffe27ddb9c5279fd9fc4R205Since the copy constructor can also be used as a normal constructor (calling it explicitly), then there might be situations where you would need the extra parameters.Agree, that was my initial design too, but in C++ you can define any number of default parameters and people coming from that background might have some cases where it is useful for them to have default parameters. Since this is the first iteration of a copy constructor implementation, we should try to stick as much as possible to the C++ design. Of course, in the case of const source we cannot do that since const means something else in D.The key difference between copy\move constructors and other constructors is that they are implicitly inserted by the compiler in certain scenarios. Documentation that describes such constructors should not be littered with alternative exotic semantics that have nothing to do with implicitly inserted function calls. this(ref typeof(this) rhs, int b) is just a constructor and should not be mentioned anywhere in the copy constructor documentation, nor in the DIP.
Jan 05 2019
On Saturday, 5 January 2019 at 14:16:41 UTC, RazvanN wrote:I think that the discussion should transcend feelings and stick to what can be done.That feeling is coming from reason I highlighted in my original post. "Can be done" is not enough, "should it be done?" is just as important.I think that discussing whether const should be transitive or not is beyond the scope of this DIP.I think no DIP should ever be viewed in isolation. The is no DIP scope. There is one, unified scope of language evolution. All concurrent DIPs and ideas must be accounted for.But the opMove DIP proposes a different syntax: https://github.com/dlang/DIPs/pull/109/files#diff-50c61cb5afd3ffe27ddb9c5279fd9fc4R205Yes, I meant I'm not a fan of that different syntax. Postblit and opPostMove should be merged into one move constructor call if it's defined IMO.Since the copy constructor can also be used as a normal constructor (calling it explicitly), then there might be situations where you would need the extra parameters.Why would you mentioned it anywhere though? It's already implemented, we have such constructors. What does it have to do with copy constructor, besides the first argument?
Jan 05 2019
On Saturday, 5 January 2019 at 14:39:06 UTC, Boris-Barboris wrote:Why would you mentioned it anywhere though? It's already implemented, we have such constructors. What does it have to do with copy constructor, besides the first argument?Prior to this DIP, one might have defined: this(ref S a, int b=0) {} This function is called only explicitly, but after this DIP, it will get called whenever a S instance will be copied. If you are against this function being a copy constructor, I can understand that, but C++ is considering it a copy constructor and for this first iteration, we are too.
Jan 05 2019
On Saturday, 5 January 2019 at 14:44:12 UTC, RazvanN wrote:This function is called only explicitly, but after this DIP, it will get called whenever a S instance will be copied. If you are against this function being a copy constructor, I can understand that, but C++ is considering it a copy constructor and for this first iteration, we are too.Oh... I didn't know C++ went that way, I now understand your reasoning, sorry. You're right, I'm against it, but if that's an expected behavior, it's probably for the best. How do they deal with ambiguity (multiple copy constructors with different argument tuples), just halt with a compiler error?
Jan 05 2019
On Saturday, 5 January 2019 at 15:00:32 UTC, Boris-Barboris wrote:On Saturday, 5 January 2019 at 14:44:12 UTC, RazvanN wrote:Yes.This function is called only explicitly, but after this DIP, it will get called whenever a S instance will be copied. If you are against this function being a copy constructor, I can understand that, but C++ is considering it a copy constructor and for this first iteration, we are too.Oh... I didn't know C++ went that way, I now understand your reasoning, sorry. You're right, I'm against it, but if that's an expected behavior, it's probably for the best. How do they deal with ambiguity (multiple copy constructors with different argument tuples), just halt with a compiler error?
Jan 05 2019
On Saturday, 5 January 2019 at 13:34:09 UTC, RazvanN wrote:On Friday, 21 December 2018 at 14:43:50 UTC, Boris-Barboris wrote:This seems like a weak justification to me. There are many things in C++ that are permitted by the language but considered poor practice by the C++ community, and continue to exist only for the sake of backward compatibility. D should not blindly adopt behavior from C++ without considering why that behavior exists in C++, and whether D could do better. I have not been able to find many sources on whether copy constructors with optional parameters are regarded as useful or good practice in the C++ community, but the results I have found are not encouraging. In the C++ Core Guidelines, the section on copy constructors [1] does not even acknowledge the possibility of additional optional parameters. In addition, at least one static analysis tool for C++ considers copy constructors with optional parameters an error [2], because they may be called implicitly in places the programmer did not intend them to be. None of this is conclusive evidence, of course, but at the very least, it indicates that this is an issue on which the C++ community is divided. If there are specific, known benefits to allowing copy constructors with optional parameters in D, then the DIP should articulate those benefits. "C++ does it that way" is not good enough. Otherwise, it may be better to go ahead with only single-parameter copy constructors, and leave the question of copy constructors with optional parameters for a future DIP. [1] https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#S-ctor [2] https://help.semmle.com/wiki/display/CCPPOBJ/Constructor+with+default+arguments+will+be+used+as+a+copy+constructor2). "A declaration is a copy constructor declaration if it is a constructor declaration that takes only one non-default parameter by reference that is of the same type as typeof(this), followed by any number of default parameters..." If you need other parameters, you are not performing a copy. Copy constructor needs no additional parameters. If the semantics of your domain problem involve parametrized post-copy operations, the code should state that explicitly - by using specialized properly-named methods, that notify the reader about this particularity.Agree, that was my initial design too, but in C++ you can define any number of default parameters and people coming from that background might have some cases where it is useful for them to have default parameters. Since this is the first iteration of a copy constructor implementation, we should try to stick as much as possible to the C++ design. Of course, in the case of const source we cannot do that since const means something else in D.
Jan 05 2019
In the section "Copy constructor call vs. blitting", the DIP explains how copying is handled when no copy constructor is defined:When a copy constructor is not defined for a struct, initializations are handled by copying the contents from the memory location of the right-hand side expression to the memory location of the left-hand side expression (i.e. "blitting"): struct A { int[] a; } void main() { A a = A([7]); A b = a; // mempcy(&b, &a) immutable A c = A([12]); immutable A d = c; // memcpy(&d, &c) }Then later, in the section "Generating copy constructors", the DIP describes under which situations copy constructors are generated implicitly:A copy constructor is generated implicitly by the compiler for a struct S if: S does not define any copy constructors; S does not have an overlapping field that defines a copy constructor; S defines at least one member that has a copy constructor. If all of the restrictions above are met, the following copy constructor is generated: this(ref inout(S) src) inout { foreach (i, ref inout field; src.tupleof) this.tupleof[i] = field; }Those two statements contradict each other. The first one clearly states that whenever no copy constructor is defined, copying is done by blitting (without exception).
Dec 22 2018
On Saturday, 22 December 2018 at 08:40:21 UTC, Johannes Loher wrote:Those two statements contradict each other. The first one clearly states that whenever no copy constructor is defined, copying is done by blitting (without exception).You are right, it's not very well stated, but I thought it would be obvious from the examples. The idea here is that when a struct does not define any copy constructors and the fields of the struct do not define any copy constructors, blitting is employed. If at least one field does define a copy constructor, then the above mentioned copy constructor is generated. Hope this sheds some light.
Jan 05 2019
On Tuesday, 18 December 2018 at 14:51:39 UTC, Mike Parker wrote:This is the feedback thread for the first round of Community Review for DIP 1018, "The Copy Constructor": https://github.com/dlang/DIPs/blob/07da1f2cabc8b1bc3ad66034598a133e5ad13356/DIPs/DIP1018.md All review-related feedback on and discussion of the DIP should occur in this thread. The review period will end at 11:59 PM ET on January 4, or when I make a post declaring it complete. (This time I'm extending the review period by a few days because of the holidays.) At the end of Round 1, if further review is deemed necessary, the DIP will be scheduled for another round of community review. Otherwise, it will be queued for the Final Review and Formal Assessment by the language maintainers. Please familiarize yourself with the documentation for the Community Review before participating. https://github.com/dlang/DIPs/blob/master/PROCEDURE.md#community-review Thanks in advance to all who participate.Motivation section needs to cover the RC GC algorithm and why the current features is unable to implement the algorithm properly while the introduction of copy constructor allows to implement it. That is the major motivation that can't be over looked. Alex.
Dec 22 2018
On Tuesday, 18 December 2018 at 14:51:39 UTC, Mike Parker wrote:This is the feedback thread for the first round of Community Review for DIP 1018, "The Copy Constructor": https://github.com/dlang/DIPs/blob/07da1f2cabc8b1bc3ad66034598a133e5ad13356/DIPs/DIP1018.md All review-related feedback on and discussion of the DIP should occur in this thread. The review period will end at 11:59 PM ET on January 4, or when I make a post declaring it complete. (This time I'm extending the review period by a few days because of the holidays.) At the end of Round 1, if further review is deemed necessary, the DIP will be scheduled for another round of community review. Otherwise, it will be queued for the Final Review and Formal Assessment by the language maintainers. Please familiarize yourself with the documentation for the Community Review before participating. https://github.com/dlang/DIPs/blob/master/PROCEDURE.md#community-review Thanks in advance to all who participate.I only realised that there would be a change to move semantics for structs that define a copy constructor at the end. Can one still manually move such structs? I'd like the DIP to go into that.
Dec 22 2018
On Saturday, 22 December 2018 at 16:40:47 UTC, Atila Neves wrote:On Tuesday, 18 December 2018 at 14:51:39 UTC, Mike Parker wrote:I think that move and copy constructors are orthogonal concepts. I don't see how copy constructors may affect this. At this point, there is no move operator in D and the copy constructor is used only when a copy is made. If you are referring to Shackar Shamesh's DIP, then yes, one can still manually move such structs. When a copy is made => copy constructor ; when a move is made => opMoveThis is the feedback thread for the first round of Community Review for DIP 1018, "The Copy Constructor": https://github.com/dlang/DIPs/blob/07da1f2cabc8b1bc3ad66034598a133e5ad13356/DIPs/DIP1018.md All review-related feedback on and discussion of the DIP should occur in this thread. The review period will end at 11:59 PM ET on January 4, or when I make a post declaring it complete. (This time I'm extending the review period by a few days because of the holidays.) At the end of Round 1, if further review is deemed necessary, the DIP will be scheduled for another round of community review. Otherwise, it will be queued for the Final Review and Formal Assessment by the language maintainers. Please familiarize yourself with the documentation for the Community Review before participating. https://github.com/dlang/DIPs/blob/master/PROCEDURE.md#community-review Thanks in advance to all who participate.I only realised that there would be a change to move semantics for structs that define a copy constructor at the end. Can one still manually move such structs? I'd like the DIP to go into that.
Jan 05 2019
On Saturday, 5 January 2019 at 13:47:44 UTC, RazvanN wrote:On Saturday, 22 December 2018 at 16:40:47 UTC, Atila Neves wrote:Perhaps, but moving and copying are not.On Tuesday, 18 December 2018 at 14:51:39 UTC, Mike Parker wrote:I think that move and copy constructors are orthogonal concepts.This is the feedback thread for the first round of Community Review for DIP 1018, "The Copy Constructor": https://github.com/dlang/DIPs/blob/07da1f2cabc8b1bc3ad66034598a133e5ad13356/DIPs/DIP1018.md All review-related feedback on and discussion of the DIP should occur in this thread. The review period will end at 11:59 PM ET on January 4, or when I make a post declaring it complete. (This time I'm extending the review period by a few days because of the holidays.) At the end of Round 1, if further review is deemed necessary, the DIP will be scheduled for another round of community review. Otherwise, it will be queued for the Final Review and Formal Assessment by the language maintainers. Please familiarize yourself with the documentation for the Community Review before participating. https://github.com/dlang/DIPs/blob/master/PROCEDURE.md#community-review Thanks in advance to all who participate.I only realised that there would be a change to move semantics for structs that define a copy constructor at the end. Can one still manually move such structs? I'd like the DIP to go into that.I don't see how copy constructors may affect this.I didn't either until the example in the DIP: ----------- struct C { this(ref C) {} } void fun(C c) {} void main() { C c; fun(c); } ----------- c is moved before the DIP, and copied afterwards due to the existing constructor being considered a copy constructor.At this point, there is no move operator in D and the copy constructor is used only when a copy is made. If you are referring to Shackar Shamesh's DIP,I wasn't referring to opMove, no, but I think adding a `this(C c)` constructor to the example would do it for rvalues?
Jan 09 2019
On Wednesday, 9 January 2019 at 14:25:44 UTC, Atila Neves wrote:I didn't either until the example in the DIP: ----------- struct C { this(ref C) {} } void fun(C c) {} void main() { C c; fun(c); } ----------- c is moved before the DIP, and copied afterwards due to the existing constructor being considered a copy constructor.C is not moved in this situation. The following code: struct C { this(this) { import std.stdio; writeln("postblit"); } } void fun(C c) {} void main() { C c; fun(c); } prints "postblit". Am I missing something?I wasn't referring to opMove, no, but I think adding a `this(C c)` constructor to the example would do it for rvalues?The copy constructor is called solely on lvalues. `this(C c)` is a simple constructor that needs to be called explicitly.
Jan 09 2019
First I want to say that I hope it gets implemented Semantic consistency demands it: B b; A a = b; //calls this(B) or this(ref B) A a2 = a; //*should* call this(ref A) I have a few points: 1) "ref" vs "ref const" dilemma The user can overload different kinds of copy ctors, but what is the *default* copy ctor ? I think the default copy ctor should be "ref const" - "ref const" is safer and fits most cases - "ref" has priority over "ref const" so the user can simply overload and his "ref" ctor will be used instead of the default 2) "Generating copy constructors" Should the user care when a constructor is "generated" if semantics are consistent ? May just say there is *always* a default copy ctor that can be overriden by the user. The implementation of default ctor can change and use memcpy as needed. 3) Not very related but maybe also needs a fix: I noticed that init to rvalue of a different type is weird right now A a1 = b; //calls this(ref B) A a2 = B(); //does not call this(ref B)
Dec 22 2018
On Saturday, 22 December 2018 at 23:26:47 UTC, Dru wrote:First I want to say that I hope it gets implemented Semantic consistency demands it: B b; A a = b; //calls this(B) or this(ref B) A a2 = a; //*should* call this(ref A)That is indeed the case.I have a few points: 1) "ref" vs "ref const" dilemma The user can overload different kinds of copy ctors, but what is the *default* copy ctor ? I think the default copy ctor should be "ref const" - "ref const" is safer and fits most cases - "ref" has priority over "ref const" so the user can simply overload and his "ref" ctor will be used instead of the defaultThere is no default copy constructor. You define the copy constructors you need. There is a generated copy constructor which is defined if a struct does not have a copy constructor, but has fields that define one. Currently, the generation strategy is pretty dumb, but I plan on enhancing it with a later DIP, in which copy constructors are generated based on the qualifiers of the fields copy constructors: struct A { this(ref A rhs) immutable {} this(ref A rhs) {} } struct B { this(ref B rhs) immutable {} } struct C { A a; B b; } For C, one can simply generate the following copy constructors: this(ref C rhs) immutable { this.a = rhs.a; this.b = rhs.b; } this(ref C rhs) { this.a = rhs.a; // copy constructor call this.b = rhs.b; // error, no copy ctor } The first one succeeds in type checking, while the second one does not. I think that this is a superior design to the one in the DIP, but Walter thinks that we should keep things simple in the first iteration and add enhancements later.2) "Generating copy constructors" Should the user care when a constructor is "generated" if semantics are consistent ? May just say there is *always* a default copy ctor that can be overriden by the user. The implementation of default ctor can change and use memcpy as needed.The copy constructor is generated, only if there are copy constructors for some fields that need to be called. The idea is that if a struct defines a copy constructor, it cannot be blitted in any circumstances; the copy constructor should always be used.3) Not very related but maybe also needs a fix: I noticed that init to rvalue of a different type is weird right now A a1 = b; //calls this(ref B) A a2 = B(); //does not call this(ref B)It cannot call it, because the constructor receives a reference and you are passing an rvalue. That is not even a copy constructor, but a regular constructor, so if you want the compiler to call it, simply delete the ref.
Jan 05 2019
The copy constructor is generated, only if there are copy constructors for some fields that need to be called. The idea is that if a struct defines a copy constructor, it cannot be blitted in any circumstances; the copy constructor should always be used.I offer a simpler logic: A "default" copy constructor is always generated, except when the user defines a constructor of the same signature. Effectively the user can override the default.It cannot call it, because the constructor receives a reference and you are passing an rvalue. That is not even a copy constructor, but a regular constructor, so if you want the compiler to call it, simply delete the ref.yes not related to the DIP just wondering why block the option to pass rvalue by ref
Jan 05 2019
On Saturday, 5 January 2019 at 14:54:41 UTC, Dru wrote:Why would you need a default copy constructor? If there aren't any copy constructors defined, the compiler will just blit the fields. Generating a single default copy constructor is not enough. Consider: struct A { int a; } fun(immutable A a) {} void main() { immutable A a; fun(a); } If we define a single copy constructor like you suggested, then this will not work because you have immutable to immutable copy. In this situation it is best to leave the copying to the existing mechanism.The copy constructor is generated, only if there are copy constructors for some fields that need to be called. The idea is that if a struct defines a copy constructor, it cannot be blitted in any circumstances; the copy constructor should always be used.I offer a simpler logic: A "default" copy constructor is always generated, except when the user defines a constructor of the same signature. Effectively the user can override the default.Currently, the only solution to this is to bind the rvalue to an lvalue: (B __tmp = B(); tmp), but this is problematic because assignment has side effects while in the original case a move is performed. It is not an unsolvable problem, but it would lead to unexpected behavior in some cases.It cannot call it, because the constructor receives a reference and you are passing an rvalue. That is not even a copy constructor, but a regular constructor, so if you want the compiler to call it, simply delete the ref.yes not related to the DIP just wondering why block the option to pass rvalue by ref
Jan 05 2019
Why would you need a default copy constructor? If there aren't any copy constructors defined, the compiler will just blit the fields. GeneratingFor the sake of consistency. Imagine if the user explicitly calls or uses the address of the (generated) copy ctor, then a struct change is made and the copy ctor is "no longer needed" because you can blit. The user code that explicitly used the constructor is now broken.a single default copy constructor is not enough.how about: this(ref const T other); immutable this(ref const T other);
Jan 09 2019
On Wednesday, 9 January 2019 at 08:18:58 UTC, Dru wrote:Consistency with what? How would you take the address of the copy constructor? &a.__ctor? As you can see, you have to use the infamous `__` to do that so you should probably avoid it. Other than that what other use cases can be made for the generation of a default copy constructor and implicitly breaking most of the code that uses structs to pass by value to functions? If you want to implicitly declare default copy constructors, you have to define all variants: mutable->mutable, const->const, immutable->immutable, shared->shared, all inout permutations etc. just to have backwards compatibility with the code that already uses structs. A horde of copy constructors to support a niche case. On the other hand, the current design is elegant as it does not touch already existent code and it generates copy constructors as they are indeed needed.Why would you need a default copy constructor? If there aren't any copy constructors defined, the compiler will just blit the fields. GeneratingFor the sake of consistency. Imagine if the user explicitly calls or uses the address of the (generated) copy ctor, then a struct change is made and the copy ctor is "no longer needed" because you can blit. The user code that explicitly used the constructor is now broken.a single default copy constructor is not enough.how about: this(ref const T other); immutable this(ref const T other);
Jan 09 2019
An argument for your side: It can be useful to check if a type is "blit able" to do that we can check if a copy ctor does not exist.
Jan 10 2019
On Tuesday, 18 December 2018 at 14:51:39 UTC, Mike Parker wrote:This is the feedback thread for the first round of Community Review for DIP 1018, "The Copy Constructor": [...]At the risk of sounding noobish, the copy constructor is only being introduce for structs and not classes correct? Alex
Jan 06 2019
On Sunday, 6 January 2019 at 22:53:41 UTC, 12345swordy wrote:On Tuesday, 18 December 2018 at 14:51:39 UTC, Mike Parker wrote:Yes.This is the feedback thread for the first round of Community Review for DIP 1018, "The Copy Constructor": [...]At the risk of sounding noobish, the copy constructor is only being introduce for structs and not classes correct? Alex
Jan 07 2019
On 2019-01-07 10:53, RazvanN wrote:On Sunday, 6 January 2019 at 22:53:41 UTC, 12345swordy wrote:Why? -- /Jacob CarlborgAt the risk of sounding noobish, the copy constructor is only being introduce for structs and not classes correct?Yes.
Jan 07 2019
On Mon, Jan 07, 2019 at 10:54:53PM +0100, Jacob Carlborg via Digitalmars-d wrote:On 2019-01-07 10:53, RazvanN wrote:https://forum.dlang.org/post/56378A55.20505 erdani.com T -- My program has no bugs! Only undocumented features...On Sunday, 6 January 2019 at 22:53:41 UTC, 12345swordy wrote:Why?At the risk of sounding noobish, the copy constructor is only being introduce for structs and not classes correct?Yes.
Jan 07 2019
On 2019-01-07 23:53, H. S. Teoh wrote:https://forum.dlang.org/post/56378A55.20505 erdani.comWould DIP1000 help with this? -- /Jacob Carlborg
Jan 09 2019