digitalmars.D - Move construction from !is(T == typeof(this))
- Manu via Digitalmars-d (68/68) Apr 23 2017 Are there any known solutions to perform efficient move construction in ...
- rikki cattermole (2/2) Apr 23 2017 There is[0] but idk how close it is to std:move and the likes.
- Manu via Digitalmars-d (3/5) Apr 24 2017 Yeah, that's not the same thing at all.
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (2/4) Apr 24 2017 std::move doesn't do anything, it is just a type-cast.
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (7/10) Apr 24 2017 What I've done in the past is simply to create a movable_ref
- Manu via Digitalmars-d (7/17) Apr 24 2017 t
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (2/4) Apr 24 2017 It answered your first question :)
- Steven Schveighoffer (9/13) Apr 24 2017 AFAIK, if you have an overload that varies solely on ref, then rvalues
- Petar Kirov [ZombineDev] (3/20) Apr 24 2017 https://issues.dlang.org/show_bug.cgi?id=17346
- Manu via Digitalmars-d (6/19) Apr 24 2017 If you're going to pinch the guts of rvalue arguments, then this needs t...
- Stanislav Blinov (55/62) Apr 24 2017 Looking over your OP again, and the report made by Petar Kirov, I
- Steven Schveighoffer (3/37) Apr 24 2017 This is what inout is for. I'll update the bug report.
- ag0aep6g (3/5) Apr 24 2017 Yup. Even in @safe code, which is a bug.
- Andrei Alexandrescu (37/42) Apr 24 2017 Should fail in all situations, thanks for reporting.
- Stanislav Blinov (42/53) Apr 24 2017 So, basically any type with const/immutable members effectively
- Andrei Alexandrescu (5/19) Apr 24 2017 If the type moved from has no elaborate postblit, a move is the same as
- Manu via Digitalmars-d (14/57) Apr 24 2017 Ah crap, I somehow missed the single-argument move() function.
- Stanislav Blinov (27/41) Apr 24 2017 No, the bug report is incorrect. See
- Manu via Digitalmars-d (14/56) Apr 25 2017 Right, yeah I see. So, basically, you admit that it is required to have ...
- Stanislav Blinov (104/118) Apr 25 2017 I admit nothing! (c) :)
- Steven Schveighoffer (12/110) Apr 26 2017 Don't do this. It's not a good idea, since data could be invalid at this...
- Stanislav Blinov (9/20) Apr 26 2017 Yeah, sorry, got carried away. Postblit would've been enough for
- Steven Schveighoffer (19/28) Apr 27 2017 My solution would be to push the duplication onto the user. I'm not a
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (4/7) Apr 28 2017 Isn't that an odd stance given that "struct" is supposed to be a
- Steven Schveighoffer (3/9) Apr 28 2017 Not really, but thanks for asking.
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (5/11) Apr 28 2017 Well, it counters the very definition of a value... I guess what
- Jack Applegame (4/6) Apr 25 2017 Because transitive const/immutable is shit. And that shit had to
- Stanislav Blinov (3/11) Apr 25 2017 Don't use it then. Life is so exciting when everything may change
- Jack Applegame (3/15) Apr 25 2017 I can't. I need immutable pointers to mutable data and vica
- Stanislav Blinov (31/64) Apr 24 2017 "Require" is a bit too strict. But yes, if the pattern is to
Are there any known solutions to perform efficient move construction in D? D's pretty good at doing moves at all the right times, but with a serious limitation compared to C++ that the type must be an exact match. Consider this C++; really bad example, but just to illustrate: struct X { std::string s; }; struct Y { std::string s; this(const X &x) { s = s; // copy the string, expensive } this(X &&x) { s = std::move(s); // claim s from x, efficient } }; Now, I'm not saying that rval references are the only solution here, just that I can overload the construction from an X for the rvalue and non-rvalue case, which is what I want... I'm thinking in D, this *might* be possible: struct X {} struct Y { this(auto ref X x) { static if (__traits(isRef, x)) { // x is lvalue, copy construct } else { // x MUST be rvalue(?), move construct // does this pattern require that I invalidate x the same way C++ does such that X's destructor won't clean up or crash? } } } Is this solid? I have a vague memory of thinking on this once and realising there was some edge case where it was logically flawed, but I can't remember clearly. Assuming this does work, the next issue is something that mirrors std::move(), is there already such a thing? Finally, a further problem exists with auto ref where the function must be a template. I have cases of code-not-available lib API's where templates are a problem. I would prefer to overload 2 constructors for the 2 cases, than have one template constructor and static if inside. I wonder what would happen in this case: struct X {} struct Y { this(ref const X x) { // x is lvalue reference, copy construct } this(X x) { // x is an lvalue... which may be a copy of another lvalue. can't move construct :/ } } I guess the question in this case is how overload selection may or may not work... I didn't test this, but I expect it's an ambiguous call given an lvalue? I wonder if this overload set could be made to work such that it is certain that the non-ref overload is only called with rvalues; ie, given this ambiguous call, ref is preferred for lvalues. rval can not call ref, therefore must resolve to byval. Where is this stuff at? - Manu
Apr 23 2017
There is[0] but idk how close it is to std:move and the likes.
Apr 23 2017
Yeah, that's not the same thing at all. On 24 April 2017 at 15:00, rikki cattermole via Digitalmars-d < digitalmars-d puremagic.com> wrote:There is[0] but idk how close it is to std:move and the likes.
Apr 24 2017
On Monday, 24 April 2017 at 05:00:13 UTC, rikki cattermole wrote:There is[0] but idk how close it is to std:move and the likes.std::move doesn't do anything, it is just a type-cast.
Apr 24 2017
On Monday, 24 April 2017 at 04:21:36 UTC, Manu wrote:Now, I'm not saying that rval references are the only solution here, just that I can overload the construction from an X for the rvalue and non-rvalue case, which is what I want...What I've done in the past is simply to create a movable_ref pointer-type. AFAICT that would be similar to C++ "&&" except it isn't downgraded when used as a parameter (which is a language feature). C++ provide that downgrading so that programmers don't accidentally forward a reference as a movable reference without making it explicit.
Apr 24 2017
On 24 April 2017 at 18:36, Ola Fosheim Gr=C3=B8stad via Digitalmars-d < digitalmars-d puremagic.com> wrote:On Monday, 24 April 2017 at 04:21:36 UTC, Manu wrote:tNow, I'm not saying that rval references are the only solution here, jus=.that I can overload the construction from an X for the rvalue and non-rvalue case, which is what I want...What I've done in the past is simply to create a movable_ref pointer-type=AFAICT that would be similar to C++ "&&" except it isn't downgraded when used as a parameter (which is a language feature). C++ provide that downgrading so that programmers don't accidentally forward a reference as=amovable reference without making it explicit.I've done that too, but that's a seriously shit solution. You didn't comment on any of my actual questions ;)
Apr 24 2017
On Monday, 24 April 2017 at 13:18:41 UTC, Manu wrote:I've done that too, but that's a seriously shit solution. You didn't comment on any of my actual questions ;)It answered your first question :)
Apr 24 2017
On 4/24/17 12:21 AM, Manu via Digitalmars-d wrote:I wonder if this overload set could be made to work such that it is certain that the non-ref overload is only called with rvalues; ie, given this ambiguous call, ref is preferred for lvalues. rval can not call ref, therefore must resolve to byval.AFAIK, if you have an overload that varies solely on ref, then rvalues go to the non-ref and lvalues go to the ref. If this is not the case, it's *intended* to be the case, and should be filed as a bug. auto ref just templates that. And in your case, it's actually clearer and cleaner not to use auto ref. Not sure if this answers your question, or if it turns out to be a viable solution. -Steve
Apr 24 2017
On Monday, 24 April 2017 at 14:00:33 UTC, Steven Schveighoffer wrote:On 4/24/17 12:21 AM, Manu via Digitalmars-d wrote:https://issues.dlang.org/show_bug.cgi?id=17346I wonder if this overload set could be made to work such that it is certain that the non-ref overload is only called with rvalues; ie, given this ambiguous call, ref is preferred for lvalues. rval can not call ref, therefore must resolve to byval.AFAIK, if you have an overload that varies solely on ref, then rvalues go to the non-ref and lvalues go to the ref. If this is not the case, it's *intended* to be the case, and should be filed as a bug. auto ref just templates that. And in your case, it's actually clearer and cleaner not to use auto ref. Not sure if this answers your question, or if it turns out to be a viable solution. -Steve
Apr 24 2017
On 25 April 2017 at 00:00, Steven Schveighoffer via Digitalmars-d < digitalmars-d puremagic.com> wrote:On 4/24/17 12:21 AM, Manu via Digitalmars-d wrote: I wonder if this overload set could be made to work such that it isIf you're going to pinch the guts of rvalue arguments, then this needs to be 100% reliable. This needs to be aggressively unit-tested, and probably documented that this is the official pattern for rvalue construction/assignment operations.certain that the non-ref overload is only called with rvalues; ie, given this ambiguous call, ref is preferred for lvalues. rval can not call ref, therefore must resolve to byval.AFAIK, if you have an overload that varies solely on ref, then rvalues go to the non-ref and lvalues go to the ref. If this is not the case, it's *intended* to be the case, and should be filed as a bug. auto ref just templates that. And in your case, it's actually clearer and cleaner not to use auto ref. Not sure if this answers your question, or if it turns out to be a viable solution.
Apr 24 2017
On Monday, 24 April 2017 at 15:19:29 UTC, Manu wrote:If you're going to pinch the guts of rvalue arguments, then this needs to be 100% reliable. This needs to be aggressively unit-tested, and probably documented that this is the official pattern for rvalue construction/assignment operations.Looking over your OP again, and the report made by Petar Kirov, I realized this is not quite right. We can't, generally speaking, treat this(const ref X x) as a copy ctor. Consider: struct X { int* ptr; } struct Y { int* ptr; this(ref const X x) { // ptr == ???; // typeof(x.ptr) == const(int*) // seems like the only way to implement this is: // ptr = new int(*x.ptr); } this(X x) { import std.algorithm.mutation : swap; swap(ptr, x.ptr); } } X x; Y y = x; // this(ref const X) is called Suddenly, we can't copy the pointer, or at least make a shallow copy of it, without violating const, and the latter is UB. The ref const overload should only be called with const lvalues, and it seems to be the case. OTOH, copying with this(ref X x) will work, as will this(X), so I guess the proper set of overloads should be: struct Y { this(ref X x) { /*copy...*/ } this(X x) { /*move...*/ } } with possible addition of this(ref const X x) for a deep copy. Speaking of const violation and UB, std.algorithm.mutation.move seems to violate const without remorse: struct Z { const int i; ~this() {} } auto z = Z(10); auto zz = move(z); // z.i is now 0 So, move does set z to a default-constructed state, but in doing so changes the value of a const variable. And there seems to be no other way to implement destructive move. In case of such Z, of course the only thing you can safely do with z now is destruct it (not that the language can enforce it), so we might say it's all good...
Apr 24 2017
On 4/24/17 2:48 PM, Stanislav Blinov wrote:On Monday, 24 April 2017 at 15:19:29 UTC, Manu wrote:This is what inout is for. I'll update the bug report. -SteveIf you're going to pinch the guts of rvalue arguments, then this needs to be 100% reliable. This needs to be aggressively unit-tested, and probably documented that this is the official pattern for rvalue construction/assignment operations.Looking over your OP again, and the report made by Petar Kirov, I realized this is not quite right. We can't, generally speaking, treat this(const ref X x) as a copy ctor. Consider: struct X { int* ptr; } struct Y { int* ptr; this(ref const X x) { // ptr == ???; // typeof(x.ptr) == const(int*) // seems like the only way to implement this is: // ptr = new int(*x.ptr); } this(X x) { import std.algorithm.mutation : swap; swap(ptr, x.ptr); } } X x; Y y = x; // this(ref const X) is called Suddenly, we can't copy the pointer, or at least make a shallow copy of it, without violating const, and the latter is UB.
Apr 24 2017
On 04/24/2017 08:48 PM, Stanislav Blinov wrote:Speaking of const violation and UB, std.algorithm.mutation.move seems to violate const without remorse:Yup. Even in safe code, which is a bug. https://issues.dlang.org/show_bug.cgi?id=15315
Apr 24 2017
On 04/24/2017 04:23 PM, ag0aep6g wrote:On 04/24/2017 08:48 PM, Stanislav Blinov wrote:Should fail in all situations, thanks for reporting. The original code should work like this: struct X {} struct Y { private X _x; this()(auto ref X x) { static if (__traits(isRef, x)) { _x = x; } else { import std.algorithm.mutation : move; _x = move(x); } } } Note how I made the ctor templated so it accepts auto ref. The less sophisticated version is simply: struct X {} struct Y { private X _x; this(ref X x) { _x = x; } this(X x) { import std.algorithm.mutation : move; _x = move(x); } } There should be ways to do this easier, i.e. add a forward() function to std.algorithm.mutation. Contributions are welcome! AndreiSpeaking of const violation and UB, std.algorithm.mutation.move seems to violate const without remorse:Yup. Even in safe code, which is a bug. https://issues.dlang.org/show_bug.cgi?id=15315
Apr 24 2017
On Monday, 24 April 2017 at 22:46:18 UTC, Andrei Alexandrescu wrote:On 04/24/2017 04:23 PM, ag0aep6g wrote:So, basically any type with const/immutable members effectively becomes immovable?On 04/24/2017 08:48 PM, Stanislav Blinov wrote:Should fail in all situations, thanks for reporting.Speaking of const violation and UB, std.algorithm.mutation.move seems to violate const without remorse:Yup. Even in safe code, which is a bug. https://issues.dlang.org/show_bug.cgi?id=15315There should be ways to do this easier, i.e. add a forward() function to std.algorithm.mutation. Contributions are welcome!Ah, thanks for the hint. There actually is one in std.functional, but it's not designed to handle one-argument situations well (returns a tuple instead of forwarding the argument). This works: import std.algorithm.mutation : move; template forward(args...) { import std.meta; static if (args.length) { static if (__traits(isRef, args[0])) alias fwd = args[0]; else property fwd() { return move(args[0]); } static if (args.length == 1) alias forward = fwd; else alias forward = AliasSeq!(fwd, forward!(args[1..$])); } else alias forward = AliasSeq!(); } struct Y { private X _x; this()(auto ref X x) { _x = forward!x; } } struct X { this(this) { writeln("copy"); } } void main() { X x; Y y = x; // outputs "copy" Y y2 = move(x); // moves, postblit not called } I'll make the PR.
Apr 24 2017
On 04/24/2017 07:13 PM, Stanislav Blinov wrote:On Monday, 24 April 2017 at 22:46:18 UTC, Andrei Alexandrescu wrote:If the type moved from has no elaborate postblit, a move is the same as a copy so those should work.On 04/24/2017 04:23 PM, ag0aep6g wrote:So, basically any type with const/immutable members effectively becomes immovable?On 04/24/2017 08:48 PM, Stanislav Blinov wrote:Should fail in all situations, thanks for reporting.Speaking of const violation and UB, std.algorithm.mutation.move seems to violate const without remorse:Yup. Even in safe code, which is a bug. https://issues.dlang.org/show_bug.cgi?id=15315I'll make the PR.Thanks! Andrei
Apr 24 2017
On 25 April 2017 at 08:46, Andrei Alexandrescu via Digitalmars-d < digitalmars-d puremagic.com> wrote:On 04/24/2017 04:23 PM, ag0aep6g wrote:Ah crap, I somehow missed the single-argument move() function. Okay, so this pattern is definitely reliable? I haven't seen it clearly documented anywhere that this is the prescribed pattern, and it's come up on stack overflow a few times, but it hasn't been answered correctly. I think this is poorly distributed knowledge. As Schveighoffer pointed out, this pattern requires *another* overload for `ref const X`? Surely if there is no `ref X` and only a `ref const X` available, it should choose that one rather than the byval one when the argument is not const (as demonstrated in the bug report?) It's demonstrated that `ref const X` might inhibit an efficient copy constructor implementation in some cases, but you can't have false-move's occurring when the lvalue is not const.On 04/24/2017 08:48 PM, Stanislav Blinov wrote:Should fail in all situations, thanks for reporting. The original code should work like this: struct X {} struct Y { private X _x; this()(auto ref X x) { static if (__traits(isRef, x)) { _x = x; } else { import std.algorithm.mutation : move; _x = move(x); } } } Note how I made the ctor templated so it accepts auto ref. The less sophisticated version is simply: struct X {} struct Y { private X _x; this(ref X x) { _x = x; } this(X x) { import std.algorithm.mutation : move; _x = move(x); } }Speaking of const violation and UB, std.algorithm.mutation.move seems to violate const without remorse:Yup. Even in safe code, which is a bug. https://issues.dlang.org/show_bug.cgi?id=15315
Apr 24 2017
On Tuesday, 25 April 2017 at 01:59:55 UTC, Manu wrote:Ah crap, I somehow missed the single-argument move() function. Okay, so this pattern is definitely reliable? I haven't seen it clearly documented anywhere that this is the prescribed pattern, and it's come up on stack overflow a few times, but it hasn't been answered correctly. I think this is poorly distributed knowledge.It is definitely reliable.As Schveighoffer pointed out, this pattern requires *another* overload for `ref const X`? Surely if there is no `ref X` and only a `ref const X` available, it should choose that one rather than the byval one when the argument is not const (as demonstrated in the bug report?)No, the bug report is incorrect. See http://dlang.org/spec/function.html#function-overloading. Remember that, unlike C++'s '&', "ref" is not a type, it's a parameter storage class. So, given the overloads foo(const ref X); foo(X x); or, more precisely: foo(ref const(X)); foo(X x); if you call foo with a non-const X lvalue *or* rvalue, the second overload will be chosen (the exact match): X x; foo(x); // typeof(x) == X, so foo(X) is chosen. It's passed by value, so x is copied first. foo(X()); // also calls foo(X), this time no copy is needed const X cx; foo(cx); // calls foo(ref const(X)), since typeof(cx) == const(X) In C++, those foos would be ambiguous. In D, they aren't, because it first checks whether it's const or not, and then whether it's an lvalue or an rvalue.It's demonstrated that `ref const X` might inhibit an efficient copy constructor implementation in some cases, but you can't have false-move's occurring when the lvalue is not const.It's not about efficiency, it's about preserving type information. Once you slap "const" on, there's no getting rid of it without violating the type system. And there won't be any "false" moves. You either have a reference to copy from, or a full-blown copy/rvalue to move from.
Apr 24 2017
On 25 April 2017 at 14:17, Stanislav Blinov via Digitalmars-d < digitalmars-d puremagic.com> wrote:On Tuesday, 25 April 2017 at 01:59:55 UTC, Manu wrote: Ah crap, I somehow missed the single-argument move() function.Right, yeah I see. So, basically, you admit that it is required to have 3 overloads; foo(X), foo(ref X) and foo(ref const X), in the event that I want to avoid needlessly copying X prior to constructing from it in the non-const case... I can imagine in some cases, copy constructing from X, and making a copy of X then move constructing from X are similar/same cost... so it's really just a gotcha that authors need to be aware of. I feel this is complicated and there are a lot of particulars. This needs to be documented clearly in the language docs, and there should probably be some examples how it relates to C++'s copy/move construction offerings, because it's significantly different and anyone with C++ experience will fail to get this right.Okay, so this pattern is definitely reliable? I haven't seen it clearly documented anywhere that this is the prescribed pattern, and it's come up on stack overflow a few times, but it hasn't been answered correctly. I think this is poorly distributed knowledge.It is definitely reliable. As Schveighoffer pointed out, this pattern requires *another* overload for`ref const X`? Surely if there is no `ref X` and only a `ref const X` available, it should choose that one rather than the byval one when the argument is not const (as demonstrated in the bug report?)No, the bug report is incorrect. See http://dlang.org/spec/function .html#function-overloading. Remember that, unlike C++'s '&', "ref" is not a type, it's a parameter storage class. So, given the overloads foo(const ref X); foo(X x); or, more precisely: foo(ref const(X)); foo(X x); if you call foo with a non-const X lvalue *or* rvalue, the second overload will be chosen (the exact match): X x; foo(x); // typeof(x) == X, so foo(X) is chosen. It's passed by value, so x is copied first. foo(X()); // also calls foo(X), this time no copy is needed const X cx; foo(cx); // calls foo(ref const(X)), since typeof(cx) == const(X) In C++, those foos would be ambiguous. In D, they aren't, because it first checks whether it's const or not, and then whether it's an lvalue or an rvalue. It's demonstrated that `ref const X` might inhibit an efficient copyconstructor implementation in some cases, but you can't have false-move's occurring when the lvalue is not const.It's not about efficiency, it's about preserving type information. Once you slap "const" on, there's no getting rid of it without violating the type system. And there won't be any "false" moves. You either have a reference to copy from, or a full-blown copy/rvalue to move from.
Apr 25 2017
On Wednesday, 26 April 2017 at 02:19:03 UTC, Manu wrote:Right, yeah I see. So, basically, you admit that it is required to have 3 overloads; foo(X), foo(ref X) and foo(ref const X), in the event that I want to avoid needlessly copying X prior to constructing from it in the non-const case...I admit nothing! (c) :) Well, yes and no. If we're talking explicit overloads, it would seem that the three overloads are needed. But why bother writing all three when the compiler can do it for you? As Andrei demonstrated, you can get away with two: struct Y { X x; this()(auto ref X x) { // assuming corrected implementation of std.functional.forward: this.x = forward!x; } this(ref const X x) { this.x = x; } } The first will handle ref/rvalue cases, the second only the ref const case. That way it's similar to what you'd have in C++: struct Y { X x; Y(const X& x) : x(x) {} Y(X&& x) : x(move(x)) {} }; Or you could even have just one: struct Y { X x; // any T that converts to const(X) this(T : const(X))(auto ref T x) { this.x = forward!x; } } which is actually awfully similar to C++, except with better constraints: struct Y { X x; template <typename T> Y(T&& x) : x(forward<T>(x)) {} }; There is a big "however". The above is not a general case. Once pointers come into play, you lose the ability to make non-const copies of const objects. So if X is, say, some custom string type (to keep in the spirit of your original C++ example): struct X { char[] data; this(string s) { data = s.dup; } this(this) { data = data.dup; } ~this() { data.destroy(); } //... } then neither constructor will compile in this case: const X cx; Y y = cx; // cannot convert const(X) to X Y y = const(X)(); // cannot convert const(X) to X In this scenario, you'll need: a) define conversion from const X to non-const X b) either drop the ref const overload altogether and use the conversion explicitly, or define all four overloads (X, ref X, const X, ref const X) with const overloads using the conversion under the hood, or get the four from two templates: X duplicate()(auto ref const X x) { return X(x.data.dup); } struct Y { X x; // X, ref X this()(auto ref X x) { this.x = forward!x; } // const(X), ref const(X) this()(auto ref const X x) { this.x = x.duplicate(); } } Steven mentioned inout, but it will be of no help here because the semantics of const/mutable copying are different. Note that this is actually redundant and nothing more than a syntactic convenience, since duplicate() already returns a non-const object. In my own code, I'd rather drop the const overloads and define a generic duplicate template that'd forward non-consts and types without pointers, and expect a dup() function to be available for all other types (similar to arrays).I can imagine in some cases, copy constructing from X, and making a copy of X then move constructing from X are similar/same cost... so it's really just a gotcha that authors need to be aware of.I feel this is complicated and there are a lot of particulars. This needs to be documented clearly in the language docs, and there should probably be some examples how it relates to C++'s copy/move construction offerings, because it's significantly different and anyone with C++ experience will fail to get this right.I agree, this is rather scattered, perhaps an article is in order.
Apr 25 2017
On 4/26/17 2:17 AM, Stanislav Blinov wrote:On Wednesday, 26 April 2017 at 02:19:03 UTC, Manu wrote:Don't do this. It's not a good idea, since data could be invalid at this point. In this case, destroy does nothing (it just sets the array to null), so I would just leave the destructor out of it.Right, yeah I see. So, basically, you admit that it is required to have 3 overloads; foo(X), foo(ref X) and foo(ref const X), in the event that I want to avoid needlessly copying X prior to constructing from it in the non-const case...I admit nothing! (c) :) Well, yes and no. If we're talking explicit overloads, it would seem that the three overloads are needed. But why bother writing all three when the compiler can do it for you? As Andrei demonstrated, you can get away with two: struct Y { X x; this()(auto ref X x) { // assuming corrected implementation of std.functional.forward: this.x = forward!x; } this(ref const X x) { this.x = x; } } The first will handle ref/rvalue cases, the second only the ref const case. That way it's similar to what you'd have in C++: struct Y { X x; Y(const X& x) : x(x) {} Y(X&& x) : x(move(x)) {} }; Or you could even have just one: struct Y { X x; // any T that converts to const(X) this(T : const(X))(auto ref T x) { this.x = forward!x; } } which is actually awfully similar to C++, except with better constraints: struct Y { X x; template <typename T> Y(T&& x) : x(forward<T>(x)) {} }; There is a big "however". The above is not a general case. Once pointers come into play, you lose the ability to make non-const copies of const objects. So if X is, say, some custom string type (to keep in the spirit of your original C++ example): struct X { char[] data; this(string s) { data = s.dup; } this(this) { data = data.dup; } ~this() { data.destroy(); }//... } then neither constructor will compile in this case: const X cx; Y y = cx; // cannot convert const(X) to X Y y = const(X)(); // cannot convert const(X) to X In this scenario, you'll need: a) define conversion from const X to non-const X b) either drop the ref const overload altogether and use the conversion explicitly, or define all four overloads (X, ref X, const X, ref const X) with const overloads using the conversion under the hood, or get the four from two templates: X duplicate()(auto ref const X x) { return X(x.data.dup); } struct Y { X x; // X, ref X this()(auto ref X x) { this.x = forward!x; } // const(X), ref const(X) this()(auto ref const X x) { this.x = x.duplicate(); } } Steven mentioned inout, but it will be of no help here because the semantics of const/mutable copying are different.inout was to help with the double indirection problem. That is, if you want to handle both const/mutable with one ref function, you need to use inout ref and not const ref, as X which contains indirections does not bind to ref const(X). If you want to duplicate const data, but just shallow-copy mutable data, you are correct in that you need two separate constructors, and inout doesn't come into play. -Steve
Apr 26 2017
On Wednesday, 26 April 2017 at 13:38:59 UTC, Steven Schveighoffer wrote:Yeah, sorry, got carried away. Postblit would've been enough for enabling destructive move.~this() { data.destroy(); }Don't do this. It's not a good idea, since data could be invalid at this point. In this case, destroy does nothing (it just sets the array to null), so I would just leave the destructor out of it.If you want to duplicate const data, but just shallow-copy mutable data, you are correct in that you need two separate constructors, and inout doesn't come into play.That's the thing, this is more about "need" than "want". As far as I understood, Manu is talking about these ctors from the C++ perspective, where the copy constructor takes const&. And the problem for those who come from C++ would be that in D it doesn't work that way, at least not universally.
Apr 26 2017
On 4/26/17 8:17 PM, Stanislav Blinov wrote:On Wednesday, 26 April 2017 at 13:38:59 UTC, Steven Schveighoffer wrote:My solution would be to push the duplication onto the user. I'm not a fan of implicit copying. It's also wasteful in the case of immutable data. struct X { char[] x; X dup() const { return X(x.dup); } } struct Y { X x; inout this(inout(X) x) { this.x = x; } } X x; const(X) cx; auto y = Y(x); auto cy = const(Y)(cx); // this could be simplified with factory y = Y(cx.dup); -SteveIf you want to duplicate const data, but just shallow-copy mutable data, you are correct in that you need two separate constructors, and inout doesn't come into play.That's the thing, this is more about "need" than "want". As far as I understood, Manu is talking about these ctors from the C++ perspective, where the copy constructor takes const&. And the problem for those who come from C++ would be that in D it doesn't work that way, at least not universally.
Apr 27 2017
On Thursday, 27 April 2017 at 12:28:38 UTC, Steven Schveighoffer wrote:My solution would be to push the duplication onto the user. I'm not a fan of implicit copying. It's also wasteful in the case of immutable data.Isn't that an odd stance given that "struct" is supposed to be a value type?
Apr 28 2017
On 4/28/17 12:56 PM, Ola Fosheim Grøstad wrote:On Thursday, 27 April 2017 at 12:28:38 UTC, Steven Schveighoffer wrote:Not really, but thanks for asking. -SteveMy solution would be to push the duplication onto the user. I'm not a fan of implicit copying. It's also wasteful in the case of immutable data.Isn't that an odd stance given that "struct" is supposed to be a value type?
Apr 28 2017
On Friday, 28 April 2017 at 18:05:41 UTC, Steven Schveighoffer wrote:On 4/28/17 12:56 PM, Ola Fosheim Grøstad wrote:Well, it counters the very definition of a value... I guess what you are saying is that the conventions are changing for struct over time, but that puts .init in a very odd position.Isn't that an odd stance given that "struct" is supposed to be a value type?Not really, but thanks for asking.
Apr 28 2017
On Monday, 24 April 2017 at 18:48:00 UTC, Stanislav Blinov wrote:Suddenly, we can't copy the pointer, or at least make a shallow copy of it, without violating const, and the latter is UB.Because transitive const/immutable is shit. And that shit had to introduce another shit - Rebindable. D const/immutable system is very big PITA. I hate it.
Apr 25 2017
On Tuesday, 25 April 2017 at 07:58:43 UTC, Jack Applegame wrote:On Monday, 24 April 2017 at 18:48:00 UTC, Stanislav Blinov wrote:Don't use it then. Life is so exciting when everything may change at any time.Suddenly, we can't copy the pointer, or at least make a shallow copy of it, without violating const, and the latter is UB.Because transitive const/immutable is shit. And that shit had to introduce another shit - Rebindable. D const/immutable system is very big PITA. I hate it.
Apr 25 2017
On Tuesday, 25 April 2017 at 08:53:22 UTC, Stanislav Blinov wrote:On Tuesday, 25 April 2017 at 07:58:43 UTC, Jack Applegame wrote:I can't. I need immutable pointers to mutable data and vica versa. D const system is better then nothing.On Monday, 24 April 2017 at 18:48:00 UTC, Stanislav Blinov wrote:Don't use it then. Life is so exciting when everything may change at any time.Suddenly, we can't copy the pointer, or at least make a shallow copy of it, without violating const, and the latter is UB.Because transitive const/immutable is shit. And that shit had to introduce another shit - Rebindable. D const/immutable system is very big PITA. I hate it.
Apr 25 2017
On Monday, 24 April 2017 at 04:21:36 UTC, Manu wrote:struct X {} struct Y { this(auto ref X x) { static if (__traits(isRef, x)) { // x is lvalue, copy construct } else { // x MUST be rvalue(?), move construct // does this pattern require that I invalidate x the same way C++ does such that X's destructor won't clean up or crash?"Require" is a bit too strict. But yes, if the pattern is to assume ownership of x's contents, then you should clear it.Is this solid? I have a vague memory of thinking on this once and realising there was some edge case where it was logically flawed, but I can't remember clearly. Assuming this does work, the next issue is something that mirrors std::move(), is there already such a thing?We can't truly override move semantics in D. The language went "all in" and left little leeway to us users in that regard. Interestingly enough, we *can* come close in cases such as the code below.struct X {} struct Y { this(ref const X x) { // x is lvalue reference, copy construct } this(X x) { // x is an lvalue... which may be a copy of another lvalue. can't move construct :/Why not? The first overload will be called with an lvalue, i.e: X x; Y y1 = x; The second one will be called in all other cases: Y y2 = X(); Y y3 = move(x); // move(x) returns X For second overload, x will be destructed once the ctor returns. I don't see any reason why you wouldn't move it's guts out.I guess the question in this case is how overload selection may or may not work... I didn't test this, but I expect it's an ambiguous call given an lvalue?There is no ambiguity there as far as I can tell. Same goes for opAssign, you can have ref opAssign(ref X x) { /*...*/ } // assign lvalue ref opAssign(X x) { /*...*/ } assign rvalue I guess you mean that you don't have source for X. But in that case, you won't be able to "move-construct" Y from X in C++ either. Nor would the compiler. If you at least know the exact memory layout of X, you could hack together a custom move: this(X x) { auto px = cast(XLayout*)cast(void*)&x; // move from px... } ...but that's neither here nor there. The words "dangerous", "non-portable" and UB march with pitchforks right behind that pattern ;)
Apr 24 2017