www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Move construction from !is(T == typeof(this))

reply Manu via Digitalmars-d <digitalmars-d puremagic.com> writes:
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
next sibling parent reply rikki cattermole <rikki cattermole.co.nz> writes:
There is[0] but idk how close it is to std:move and the likes.


Apr 23 2017
next sibling parent Manu via Digitalmars-d <digitalmars-d puremagic.com> writes:
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
prev sibling parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= writes:
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
prev sibling next sibling parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= writes:
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
parent reply Manu via Digitalmars-d <digitalmars-d puremagic.com> writes:
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:

 Now, I'm not saying that rval references are the only solution here, jus=
t
 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.
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
parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= writes:
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
prev sibling next sibling parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
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
next sibling parent Petar Kirov [ZombineDev] <petar.p.kirov gmail.com> writes:
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:

 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
https://issues.dlang.org/show_bug.cgi?id=17346
Apr 24 2017
prev sibling parent reply Manu via Digitalmars-d <digitalmars-d puremagic.com> writes:
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 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.
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.
Apr 24 2017
parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
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
next sibling parent Steven Schveighoffer <schveiguy yahoo.com> writes:
On 4/24/17 2:48 PM, Stanislav Blinov wrote:
 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.
This is what inout is for. I'll update the bug report. -Steve
Apr 24 2017
prev sibling next sibling parent reply ag0aep6g <anonymous example.com> writes:
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
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 04/24/2017 04:23 PM, ag0aep6g wrote:
 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
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! Andrei
Apr 24 2017
next sibling parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Monday, 24 April 2017 at 22:46:18 UTC, Andrei Alexandrescu 
wrote:
 On 04/24/2017 04:23 PM, ag0aep6g wrote:
 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
Should fail in all situations, thanks for reporting.
So, basically any type with const/immutable members effectively becomes immovable?
 There 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
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 04/24/2017 07:13 PM, Stanislav Blinov wrote:
 On Monday, 24 April 2017 at 22:46:18 UTC, Andrei Alexandrescu wrote:
 On 04/24/2017 04:23 PM, ag0aep6g wrote:
 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
Should fail in all situations, thanks for reporting.
So, basically any type with const/immutable members effectively becomes immovable?
If the type moved from has no elaborate postblit, a move is the same as a copy so those should work.
 I'll make the PR.
Thanks! Andrei
Apr 24 2017
prev sibling parent reply Manu via Digitalmars-d <digitalmars-d puremagic.com> writes:
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:

 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
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); } }
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.
Apr 24 2017
parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
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
parent reply Manu via Digitalmars-d <digitalmars-d puremagic.com> writes:
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.
 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.
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.
Apr 25 2017
parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
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
parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 4/26/17 2:17 AM, Stanislav Blinov wrote:
 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(); }
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.
     //...
 }

 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
parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Wednesday, 26 April 2017 at 13:38:59 UTC, Steven Schveighoffer 
wrote:

     ~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.
Yeah, sorry, got carried away. Postblit would've been enough for enabling destructive move.
 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
parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 4/26/17 8:17 PM, Stanislav Blinov wrote:
 On Wednesday, 26 April 2017 at 13:38:59 UTC, Steven Schveighoffer wrote:
 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.
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); -Steve
Apr 27 2017
parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= writes:
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
parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
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:
 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?
Not really, but thanks for asking. -Steve
Apr 28 2017
parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= writes:
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:
 Isn't that an odd stance given that "struct" is supposed to be 
 a value
 type?
Not really, but thanks for asking.
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.
Apr 28 2017
prev sibling parent reply Jack Applegame <japplegame gmail.com> writes:
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
parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
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:
 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.
Don't use it then. Life is so exciting when everything may change at any time.
Apr 25 2017
parent Jack Applegame <japplegame gmail.com> writes:
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:
 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.
Don't use it then. Life is so exciting when everything may change at any time.
I can't. I need immutable pointers to mutable data and vica versa. D const system is better then nothing.
Apr 25 2017
prev sibling parent Stanislav Blinov <stanislav.blinov gmail.com> writes:
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