www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - D's equivalent to C++'s std::move?

reply Shachar Shemesh <shachar weka.io> writes:
Hi all,

I have a non-copyable struct with move semantics. In other words, a 
struct with  disable this(this), but with working overloads for the 
this(copy) and opAssign.

Now I have an instance of that struct. I would like to be able to 
voluntarily give up ownership for the sake of another instance.

In C++, I would do something like this:

unique_ptr<int> p (new int), q;

q = std::move(p);

I am unsure what is the correct way to do this under D.

Shachar
Feb 01 2016
next sibling parent Rikki Cattermole <alphaglosined gmail.com> writes:
On 02/02/16 2:21 AM, Shachar Shemesh wrote:
 Hi all,

 I have a non-copyable struct with move semantics. In other words, a
 struct with  disable this(this), but with working overloads for the
 this(copy) and opAssign.

 Now I have an instance of that struct. I would like to be able to
 voluntarily give up ownership for the sake of another instance.

 In C++, I would do something like this:

 unique_ptr<int> p (new int), q;

 q = std::move(p);

 I am unsure what is the correct way to do this under D.

 Shachar
So just to confirm, you want to explicitly copy a struct but not "implicitly" copy it? struct Foo { disable this(this); int x; Foo dup() { return Foo(x); } } void main() { Foo a, b; a = Foo(7); b = a.dup; // will error // b = a; } Please note for this example code I have implemented dup, you wouldn't normally need to do that for structs. Also D.learn is correct place to ask this. No the general N.G.
Feb 01 2016
prev sibling next sibling parent =?UTF-8?Q?S=c3=b6nke_Ludwig?= <sludwig outerproduct.org> writes:
Am 01.02.2016 um 14:21 schrieb Shachar Shemesh:
 Hi all,

 I have a non-copyable struct with move semantics. In other words, a
 struct with  disable this(this), but with working overloads for the
 this(copy) and opAssign.

 Now I have an instance of that struct. I would like to be able to
 voluntarily give up ownership for the sake of another instance.

 In C++, I would do something like this:

 unique_ptr<int> p (new int), q;

 q = std::move(p);

 I am unsure what is the correct way to do this under D.

 Shachar
Should work with move() from std.algorithm: http://dlang.org/library/std/algorithm/mutation/move.html
Feb 01 2016
prev sibling next sibling parent Dicebot <public dicebot.lv> writes:
On Monday, 1 February 2016 at 13:21:02 UTC, Shachar Shemesh wrote:
 Hi all,

 I have a non-copyable struct with move semantics. In other 
 words, a struct with  disable this(this), but with working 
 overloads for the this(copy) and opAssign.

 Now I have an instance of that struct. I would like to be able 
 to voluntarily give up ownership for the sake of another 
 instance.
auto move (T) (ref T origin) if (is(T == struct)) { scope(exit) origin = T.init; return T(origin.tupleof); } // example: struct S { int* x; disable this(this); } void foo (S s) { } void main() { auto s = S(new int); // won't compile, postblit is disabled: foo(s); // but this works, because rvalues are always moved: foo(move(s)); assert(s.x is null); }
Feb 01 2016
prev sibling next sibling parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= writes:
On Monday, 1 February 2016 at 13:21:02 UTC, Shachar Shemesh wrote:
 q = std::move(p);

 I am unsure what is the correct way to do this under D.
Note that C++ std::move(...) doesn't do anything related to state, it is only a type cast, so it is zero-overhead. What I have done to get semantics close to C++ is to define "moving" type and a "pointer" type that use it. (I dislike how D deals with this.)
Feb 01 2016
parent reply maik klein <maikklein googlemail.com> writes:
On Monday, 1 February 2016 at 13:52:49 UTC, Ola Fosheim Grøstad 
wrote:
 On Monday, 1 February 2016 at 13:21:02 UTC, Shachar Shemesh 
 wrote:
 q = std::move(p);

 I am unsure what is the correct way to do this under D.
Note that C++ std::move(...) doesn't do anything related to state, it is only a type cast, so it is zero-overhead. What I have done to get semantics close to C++ is to define "moving" type and a "pointer" type that use it. (I dislike how D deals with this.)
Could you share this? I would be very interested in how you have approached it.
Feb 02 2016
parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= writes:
On Tuesday, 2 February 2016 at 21:18:27 UTC, maik klein wrote:
 Could you share this? I would be very interested in how you 
 have approached it.
My ad hoc pointer experiments try to be general and is convoluted, so not the best example. The basic idea is to have a pointer type that match "&&" in C++ (which cannot be done completely). So we have something like: struct moving(T){ T* ptr; ~this(){ assert(ptr is null); } // optional check to ensure that the move is completed } then have a move() function that returns a moving!T struct In you pointer struct you match opAssign(moving!T ...) to emulate "operator=(T&& ...)" and set the ptr field to null to prevent it from being used again (if you want that extra check).
Feb 02 2016
prev sibling next sibling parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 02/01/2016 05:21 AM, Shachar Shemesh wrote:

 I have a non-copyable struct with move semantics. In other words, a
 struct with  disable this(this), but with working overloads for the
 this(copy) and opAssign.

 Now I have an instance of that struct. I would like to be able to
 voluntarily give up ownership for the sake of another instance.

 In C++, I would do something like this:

 unique_ptr<int> p (new int), q;

 q = std::move(p);

 I am unsure what is the correct way to do this under D.
This question has been brought up a lot lately. I've decided to look at this more seriously yesterday. My first observation is that if post-blit is disabled isn't the struct already a unique type? If so, we don't need unique_ptr for variables of that type, right? Am I completely off there? vector<unique_ptr<T>> is a valid use case but it's not possible with D's arrays because D's arrays work with .init object, which require assigning into. So, I've played with an array type that can store non-copyable types. The main functions are emplace_back() and move_back(). One requirement is that the stored type must be idempotent regarding destruction of its .init value. Have you used something similar before? Is this a correct approach to this problem? (Pardon the non-D naming convention that uses underscores.) /* An array that can store non-copyable types. */ struct UniqueArray(T) { ubyte[] storage; size_t count; disable this(this); this(size_t size) { storage = new ubyte[](size); } ~this() { foreach (i; 0 .. count) { destroy_at(i); } } /* Returns the address of element at index 'i'.*/ T* addressOf(size_t i) { const offset = T.sizeof * i; return cast(T*)(storage.ptr + offset); } /* Returns a range to all elements. TODO: Use operator overloading. */ auto all() { auto arr = (cast(T*)(storage.ptr))[0..count]; T*[] result; foreach (ref i; arr) { result ~= &i; } return result; } import std.typecons : Flag, Yes, No; /* Emplaces a new object at index 'i' with the given constructor * arguments. */ T* emplace_at(Flag!"occupied" occupied = Yes.occupied, Args...)(size_t i, Args args) { import std.exception : enforce; import std.conv : emplace; enforce(i <= count); T* place = addressOf(i); /* TODO: Be exception-safe; don't destroy before succesful * construction. */ if (occupied) { destroy_at(i); } emplace(place, args); return place; } /* Emplaces an object at the end with the given constructor arguments. */ T* emplace_back(Args...)(Args args) { if (storage.length == (count * T.sizeof)) { storage.length += T.sizeof; } const isOccupied = false; T* place = emplace_at!(No.occupied)(count, args); ++count; return place; } /* Moves an lvalue to the end. The arguments becomes T.init. */ void move_back(ref T s) { import std.algorithm : move; T* place = emplace_back(); move(s, *place); } /* Destroys the element at index 'i'. */ void destroy_at(size_t i) { destroy(*addressOf(i)); } } UniqueArray!S uniqueArray(S)(size_t size = 0) { return UniqueArray!S(size); } /* Test code follows. */ import std.stdio; /* A non-copyable type. */ struct S { int i = -1; disable this(this); void printInfo(string func = __FUNCTION__)() { writefln("%s for %s", func, i); } this(int i) { this.i = i; printInfo(); } ~this() { printInfo(); if (i == -1) { /* This type does not do anything special for its .init value. */ writefln(" ... (Skipping cleanup for .init)"); } else { writefln(" Doing proper cleanup"); } } } void main() { auto u = uniqueArray!S(); /* Some are emplaced back, some are moved back. */ foreach (i; 0 .. 5) { if (i % 2) { writefln("Emplacing rvalue %s back", i); u.emplace_back(i); } else { writefln("Making lvalue %s", i); auto s = S(i); writefln("Moving lvalue %s back", i); u.move_back(s); assert(s == S.init); } } writefln("Replacing 2 with 100"); u.emplace_at(2, 100); writefln("Destroying %s", 1); u.destroy_at(1); /* Just do someting with element states. NOTE: writeln(u) does not work * because 'u' is not copyable. */ foreach (i, ref e; u.all) { writefln("%s: %s", i, e.i); } writefln("Leaving main"); } For convenience, here is the output of the program: Making lvalue 0 deneme.S.this for 0 Moving lvalue 0 back deneme.S.~this for -1 ... (Skipping cleanup for .init) deneme.S.~this for -1 ... (Skipping cleanup for .init) Emplacing rvalue 1 back deneme.S.this for 1 Making lvalue 2 deneme.S.this for 2 Moving lvalue 2 back deneme.S.~this for -1 ... (Skipping cleanup for .init) deneme.S.~this for -1 ... (Skipping cleanup for .init) Emplacing rvalue 3 back deneme.S.this for 3 Making lvalue 4 deneme.S.this for 4 Moving lvalue 4 back deneme.S.~this for -1 ... (Skipping cleanup for .init) deneme.S.~this for -1 ... (Skipping cleanup for .init) Replacing 2 with 100 deneme.S.~this for 2 Doing proper cleanup deneme.S.this for 100 Destroying 1 deneme.S.~this for 1 Doing proper cleanup 0: 0 1: -1 2: 100 3: 3 4: 4 Leaving main deneme.S.~this for 0 Doing proper cleanup deneme.S.~this for -1 ... (Skipping cleanup for .init) deneme.S.~this for 100 Doing proper cleanup deneme.S.~this for 3 Doing proper cleanup deneme.S.~this for 4 Doing proper cleanup Ali
Feb 02 2016
next sibling parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= writes:
On Tuesday, 2 February 2016 at 22:36:22 UTC, Ali Çehreli wrote:
 This question has been brought up a lot lately. I've decided to 
 look at this more seriously yesterday.
Nice to see that others are playing around with this, I don't have time to check your code, but one key issue with move semantics is exception safety. AFAICT D's "std.move" is insufficient, as it would null out the original pointer prematurely and when an exception is thrown the resource will disappear rather than simple remain "unmoved".
Feb 02 2016
parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 02/02/2016 03:09 PM, Ola Fosheim Grøstad wrote:
 On Tuesday, 2 February 2016 at 22:36:22 UTC, Ali Çehreli wrote:
 This question has been brought up a lot lately. I've decided to look
 at this more seriously yesterday.
Nice to see that others are playing around with this, I don't have time to check your code, but one key issue with move semantics is exception safety. AFAICT D's "std.move" is insufficient, as it would null out the original pointer prematurely and when an exception is thrown the resource will disappear rather than simple remain "unmoved".
Exactly. I've saved my rear end by inserting a TODO comment just before posting the code: :p /* TODO: Be exception-safe; don't destroy before succesful * construction. */ if (occupied) { destroy_at(i); } emplace(place, args); Ali
Feb 02 2016
parent reply =?UTF-8?Q?S=c3=b6nke_Ludwig?= <sludwig outerproduct.org> writes:
Am 03.02.2016 um 00:21 schrieb Ali Çehreli:
 On 02/02/2016 03:09 PM, Ola Fosheim Grøstad wrote:
 On Tuesday, 2 February 2016 at 22:36:22 UTC, Ali Çehreli wrote:
 This question has been brought up a lot lately. I've decided to look
 at this more seriously yesterday.
Nice to see that others are playing around with this, I don't have time to check your code, but one key issue with move semantics is exception safety. AFAICT D's "std.move" is insufficient, as it would null out the original pointer prematurely and when an exception is thrown the resource will disappear rather than simple remain "unmoved".
Exactly. I've saved my rear end by inserting a TODO comment just before posting the code: :p /* TODO: Be exception-safe; don't destroy before succesful * construction. */ if (occupied) { destroy_at(i); } emplace(place, args); Ali
For std.move, isn't the only place where an exception can be thrown in the destructor (which shouldn't throw)? It uses memcpy to move the memory around to circumvent any extended construction logic.
Feb 03 2016
next sibling parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= writes:
On Wednesday, 3 February 2016 at 15:05:39 UTC, Sönke Ludwig wrote:
 For std.move, isn't the only place where an exception can be 
 thrown in the destructor (which shouldn't throw)? It uses 
 memcpy to move the memory around to circumvent any extended 
 construction logic.
Not sure if you are talking about something else, but in C++ if you do "somefunction(std::move(resource))" then somefunction can throw and the resource remains untouched (if implemented in a reasonable fashion). In D, std.move(...) has this implementation: private T moveImpl(T)(ref T source) { T result = void; moveEmplace(source, result); return result; } So clearly by the time somefunction is called, the resource is already moved and an exception will cause permanent damage?
Feb 03 2016
parent reply =?UTF-8?Q?S=c3=b6nke_Ludwig?= <sludwig outerproduct.org> writes:
Am 03.02.2016 um 16:29 schrieb Ola Fosheim Grøstad:
 On Wednesday, 3 February 2016 at 15:05:39 UTC, Sönke Ludwig wrote:
 For std.move, isn't the only place where an exception can be thrown in
 the destructor (which shouldn't throw)? It uses memcpy to move the
 memory around to circumvent any extended construction logic.
Not sure if you are talking about something else, but in C++ if you do "somefunction(std::move(resource))" then somefunction can throw and the resource remains untouched (if implemented in a reasonable fashion). In D, std.move(...) has this implementation: private T moveImpl(T)(ref T source) { T result = void; moveEmplace(source, result); return result; } So clearly by the time somefunction is called, the resource is already moved and an exception will cause permanent damage?
Hmm, that's true, it would get destroyed and you'd have to let somefunction take the argument by reference to avoid that. But in general I don't see an issue with this. Once the value is moved into the context of somefunction, somefunction has ownership and needs to take care of where the value goes - seems like pretty clear semantics. And in C++ you'd have the same situation once somefunction decides to move/swap the value somewhere else before throwing an exception.
Feb 03 2016
parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= writes:
On Wednesday, 3 February 2016 at 15:44:25 UTC, Sönke Ludwig wrote:
 seems like pretty clear semantics. And in C++ you'd have the 
 same situation once somefunction decides to move/swap the value 
 somewhere else before throwing an exception.
Well, you can always move it back or wait with the move. Also, std.move may end up being inefficient when you have a complicated resource holder. Since the work is done before calling the function the optimizer may struggle with getting rid of the work.
Feb 03 2016
next sibling parent =?UTF-8?Q?S=c3=b6nke_Ludwig?= <sludwig outerproduct.org> writes:
Am 03.02.2016 um 16:56 schrieb Ola Fosheim Grøstad:
 On Wednesday, 3 February 2016 at 15:44:25 UTC, Sönke Ludwig wrote:
 seems like pretty clear semantics. And in C++ you'd have the same
 situation once somefunction decides to move/swap the value somewhere
 else before throwing an exception.
Well, you can always move it back or wait with the move.
Yeah, a ref parameter is more or less the only similar option.
 Also, std.move may end up being inefficient when you have a complicated
 resource holder. Since the work is done before calling the function the
 optimizer may struggle with getting rid of the work.
That's probably indeed true since it relies on memcpy. You can of course still use dynamic allocation + unique reference, or pass-by-reference, to avoid the cost of the copy. But it does sound like a worthwhile optimization target.
Feb 03 2016
prev sibling parent reply Lars T. Kyllingstad <public kyllingen.net> writes:
On Wednesday, 3 February 2016 at 15:56:48 UTC, Ola Fosheim 
Grøstad wrote:
 On Wednesday, 3 February 2016 at 15:44:25 UTC, Sönke Ludwig 
 wrote:
 seems like pretty clear semantics. And in C++ you'd have the 
 same situation once somefunction decides to move/swap the 
 value somewhere else before throwing an exception.
Well, you can always move it back or wait with the move. Also, std.move may end up being inefficient when you have a complicated resource holder. Since the work is done before calling the function the optimizer may struggle with getting rid of the work.
In my experience, in the vast majority of cases a C++ move operation boils down to a memberwise copy (of value types) or copy-and-reset (of reference types). With the extra logic and program flow that is sometimes involved in move construction and move assignment, I suspect that a straightforward double memcpy as it is done in D will be almost as performant or moreso most of the time. Add to that the fact that a lot of programmers out there will implement move construction in terms of move assignment -- which makes it a default construction PLUS move -- and move assignment in terms of swap -- i.e., three moves -- for the sake of DRY. Personally, I think D's move semantics are actually clearer and easier to get right. It is somewhat unfortunate that you cannot provide the strong exception guarantee for a function when you move arguments into it, though, but the semantics are pretty clear and easy to explain to newbies: If you use std.move() on something it definitely gets moved. In C++, if you use std::move() on something it may or may not be moved; it depends on the recipient of the move. Lars
Feb 13 2016
parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= writes:
On Saturday, 13 February 2016 at 09:11:06 UTC, Lars T. 
Kyllingstad wrote:
 In my experience, in the vast majority of cases a C++ move 
 operation boils down to a memberwise copy (of value types) or 
 copy-and-reset (of reference types).  With the extra logic and 
 program flow that is sometimes involved in move construction 
 and move assignment, I suspect that a straightforward double 
 memcpy as it is done in D will be almost as performant or 
 moreso most of the time.
No? Not in the libraries I write. A move typically just means copying 1-4 64 bit values and setting a single 64 bit value in the source. Usually written as an inline function. Or nothing, in the case where the logic does not end up with a move, something which D cannot represent with the same semantic distinction.
 Add to that the fact that a lot of programmers out there will 
 implement move construction in terms of move assignment -- 
 which makes it a default construction PLUS move -- and move 
 assignment in terms of swap -- i.e., three moves -- for the 
 sake of DRY.
Huh? Move assignments is no different from move construction, except you have to release the existing value if the receiving object isn't empty. Constructing an empty resource owner object usually just means setting a single field to zero, which is inlined and removed if it is followed by an assignment.
  Personally, I think D's move semantics are actually clearer 
 and easier to get right.
But I don't think D has move semantics. I don't think it makes for correctness for resource ownership.
 explain to newbies:  If you use std.move() on something it 
 definitely gets moved.  In C++, if you use std::move() on 
 something it may or may not be moved; it depends on the 
 recipient of the move.
No? With D's std.move() the resource can be destroyed or get into an inconsistent state if the caller does it wrong? Or can the type system help you?
Feb 13 2016
parent reply Lars T. Kyllingstad <public kyllingen.net> writes:
On Saturday, 13 February 2016 at 12:14:43 UTC, Ola Fosheim 
Grøstad wrote:
 On Saturday, 13 February 2016 at 09:11:06 UTC, Lars T. 
 Kyllingstad wrote:
 In my experience, in the vast majority of cases a C++ move 
 operation boils down to a memberwise copy (of value types) or 
 copy-and-reset (of reference types).  With the extra logic and 
 program flow that is sometimes involved in move construction 
 and move assignment, I suspect that a straightforward double 
 memcpy as it is done in D will be almost as performant or 
 moreso most of the time.
No? Not in the libraries I write. A move typically just means copying 1-4 64 bit values and setting a single 64 bit value in the source. Usually written as an inline function.
Not knowing anything about the libraries you write, it's hard to argue with that. But I agree that given that you are in control of all the code AND can make the move ctor/assignment available for inlining (AND are an experienced programmer), then yes, you can most definitely get better performance with C++'s move() than with D's move(). But consider the more general case where you have an object of type 'struct A', which is embedded in an object of type 'struct B', which is again embedded in an object of type 'struct C', and so on, and where A, B, and C are perhaps in separate libraries or for some other reason their move ctors/assigments cannot be inlined. Then, you are looking at multiple levels of function calls and you are also at the mercy of whoever wrote their move code. In D the cost of a move is very predictable and should be performant enough for most use cases. And for the ones where it absolutely isn't, I'm sure making a custom solution is feasible.
 Or nothing, in the case where the logic does not end up with a 
 move, something which D cannot represent with the same semantic 
 distinction.

 Add to that the fact that a lot of programmers out there will 
 implement move construction in terms of move assignment -- 
 which makes it a default construction PLUS move -- and move 
 assignment in terms of swap -- i.e., three moves -- for the 
 sake of DRY.
Huh? Move assignments is no different from move construction, except you have to release the existing value if the receiving object isn't empty. Constructing an empty resource owner object usually just means setting a single field to zero, which is inlined and removed if it is followed by an assignment.
What I meant is that you will find a lot of C++ code out there, written by well-meaning programmers, that looks like this: class C { C(C&& other) { operator=(std::move(other)); } // and/or C& operator=(C&& other) { swap(*this, other); return *this; } }; Here, you have unnecessary construction of C's members in the constructor which may or may not be optimised away before the assignment. Furthermore, you have an unnecessary number of moves in the assignment operator -- plus the potential drawbacks of deferred release of the resource.
  Personally, I think D's move semantics are actually clearer 
 and easier to get right.
But I don't think D has move semantics. I don't think it makes for correctness for resource ownership.
I'm not sure what you mean by "has move semantics" here. It does not have C++'s move semantics, no, but I would say D has its own move semantics. It has a move() function that transfers raw state between objects, and D structs are supposed to be designed so they are movable by means of raw bit transfer, allowing the compiler and GC to move them around as it sees fit. But maybe I'm missing something?
 explain to newbies:  If you use std.move() on something it 
 definitely gets moved.  In C++, if you use std::move() on 
 something it may or may not be moved; it depends on the 
 recipient of the move.
No? With D's std.move() the resource can be destroyed or get into an inconsistent state if the caller does it wrong?
I guess this is what I don't understand. How and when does that happen? Lars
Feb 13 2016
parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= writes:
On Saturday, 13 February 2016 at 17:47:54 UTC, Lars T. 
Kyllingstad wrote:
 Not knowing anything about the libraries you write, it's hard 
 to argue with that.  But I agree that given that you are in 
 control of all the code AND can make the move ctor/assignment 
 available for inlining (AND are an experienced programmer),
C++ does indeed put the burden on the library programmer and is not a good language for "non-professional" use. But it is flexible by providing the mechanisms in the type system rather than an opinionated solution. (Of course, parts of the C++ standard library is opinionated.)
 cannot be inlined.  Then, you are looking at multiple levels of 
 function calls and you are also at the mercy of whoever wrote 
 their move code.
Well, I primarily use move semantics for ownership, like owning resources in the GPU, files system, memory etc. So it usually is 1 or 2 levels.
 Here, you have unnecessary construction of C's members in the 
 constructor which may or may not be optimised away before the 
 assignment.
Well, doing a swap would break the expectations for assignment...
  Furthermore, you have an unnecessary number of moves in the 
 assignment operator -- plus the potential drawbacks of deferred 
 release of the resource.
I don't understand what you mean by unnecessary moves? std::move/std::forward are just type casting so they don't result in code...
 I'm not sure what you mean by "has move semantics" here.  It 
 does not have C++'s move semantics, no, but I would say D has 
 its own move semantics.  It has a move() function that 
 transfers raw state between objects, and D structs are supposed 
 to be designed so they are movable by means of raw bit 
 transfer, allowing the compiler and GC to move them around as 
 it sees fit.  But maybe I'm missing something?
Well, but that is like saying that C++03 also had move semantics. There is nothing special about D's move(), it's just a library function?
 No? With D's std.move() the resource can be destroyed or get 
 into an inconsistent state if the caller does it wrong?
I guess this is what I don't understand. How and when does that happen?
The std.move() actually does a copy, then copy the init value to the original. If something happens that prevents the value from being preserved the object will be destroyed by the destructors. I.e. an exception. And worse, if you have back pointers to it, it will end up being inconsistent. There is no way the type system can prevent back pointers without preventing D from being usable as a language. Since you no longer have the original object... shit can happen. In C++ you can set a mutex in the object and fix things because you have the full object. So if someone tries to follow the back pointer the mutex will block. You can probably come up with many other scenarios. "postblit" does not fix this (not a very elegant solution IMO). So, C++ gives the library author control by having "move" be part of the type system that essentially does nothing else than applying some constraints. Having "move" as an action is both limiting and potentially flawed, since the D compiler does not do anything to ensure correctness. If "move" is an action, rather than a type system constraint, then it should be backed up with semantic analysis IMO.
Feb 13 2016
next sibling parent reply rsw0x <anonymous anonymous.com> writes:
On Saturday, 13 February 2016 at 19:25:37 UTC, Ola Fosheim 
Grøstad wrote:
 I'm not sure what you mean by "has move semantics" here.  It 
 does not have C++'s move semantics, no, but I would say D has 
 its own move semantics.  It has a move() function that 
 transfers raw state between objects, and D structs are 
 supposed to be designed so they are movable by means of raw 
 bit transfer, allowing the compiler and GC to move them around 
 as it sees fit.  But maybe I'm missing something?
Well, but that is like saying that C++03 also had move semantics. There is nothing special about D's move(), it's just a library function?
D "guarantees" NRVO which is what enables its move semantics, C++ did/does not. Quotes because IIRC(?) it used to be part of the spec and it isn't anymore, I don't think Walter or Andrei have addressed this yet so I'm not sure if it's intended.
Feb 13 2016
parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= writes:
On Saturday, 13 February 2016 at 20:11:45 UTC, rsw0x wrote:
 D "guarantees" NRVO which is what enables its move semantics, 
 C++ did/does not.

 Quotes because IIRC(?) it used to be part of the spec and it 
 isn't anymore, I don't think Walter or Andrei have addressed 
 this yet so I'm not sure if it's intended.
By NRVO I assume you mean: https://en.wikipedia.org/wiki/Return_value_optimization All the common C++ compilers do RVO, but you don't need that to implement move semantic-like behaviour in C++03. I wouldn't call RVO move semantics at all...
Feb 13 2016
parent reply rsw0x <anonymous anonymous.com> writes:
On Saturday, 13 February 2016 at 20:24:12 UTC, Ola Fosheim 
Grøstad wrote:
 On Saturday, 13 February 2016 at 20:11:45 UTC, rsw0x wrote:
 D "guarantees" NRVO which is what enables its move semantics, 
 C++ did/does not.

 Quotes because IIRC(?) it used to be part of the spec and it 
 isn't anymore, I don't think Walter or Andrei have addressed 
 this yet so I'm not sure if it's intended.
By NRVO I assume you mean: https://en.wikipedia.org/wiki/Return_value_optimization All the common C++ compilers do RVO, but you don't need that to implement move semantic-like behaviour in C++03. I wouldn't call RVO move semantics at all...
It's not move semantics, it enables move semantics. Yes, C++ compilers do it(Walter 'invented' it, fyi) but C++ doesn't guarantee it. D (is supposed) to) guarantee it, which enables move semantics. i.e, see https://issues.dlang.org/show_bug.cgi?id=5777
Feb 13 2016
parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= writes:
On Saturday, 13 February 2016 at 20:39:22 UTC, rsw0x wrote:
 It's not move semantics, it enables move semantics.
 Yes, C++ compilers do it(Walter 'invented' it, fyi) but C++ 
 doesn't guarantee it. D (is supposed) to) guarantee it, which 
 enables move semantics.
I understand what you mean. You mean construction of read/write protected objects, which you can override with dedicated functions with special privileges. But you can do that in old C++ as well... You only need to create your own reference type. Of course, there is not much to invent as the common C paradigm for objects has always been that kind of initialization which RVO "emulates": data_t data; initialize_data(&data); The primary difference is that C does not provide any protection for the data. The issue in C++ is that it is not an optimization, as it breaks the language semantics (which the standard now allows).
Feb 13 2016
prev sibling parent reply Lars T. Kyllingstad <public kyllingen.net> writes:
On Saturday, 13 February 2016 at 19:25:37 UTC, Ola Fosheim 
Grøstad wrote:
 On Saturday, 13 February 2016 at 17:47:54 UTC, Lars T. 
 Kyllingstad wrote:
 [...]
C++ does indeed put the burden on the library programmer and is not a good language for "non-professional" use. But it is flexible by providing the mechanisms in the type system rather than an opinionated solution. [...]
D is all about opinionated solutions. :) In fact, I would go so far as to say that's what sets D apart from C++, and mostly in a good way.
 [...]

 Here, you have unnecessary construction of C's members in the 
 constructor which may or may not be optimised away before the 
 assignment.
Well, doing a swap would break the expectations for assignment...
Whose expectations? The formal expectation, as per the C++ standard, is that the moved-from object be left in a "valid but unspecified state". Basically, as long as it is safe to destroy or reassign to the moved-from object, you're good. I hope this is not coming across as me endorsing the practice of implementing move assignment in terms of swap, because I don't. But it *is* a rather common practice, enough so that Scott Meyers felt the need to write an article about it: http://scottmeyers.blogspot.no/2014/06/the-drawbacks-of-implementing-move.html
  Furthermore, you have an unnecessary number of moves in the 
 assignment operator -- plus the potential drawbacks of 
 deferred release of the resource.
I don't understand what you mean by unnecessary moves? std::move/std::forward are just type casting so they don't result in code...
A swap is three moves -- actual moves.
 I'm not sure what you mean by "has move semantics" here.  It 
 does not have C++'s move semantics, no, but I would say D has 
 its own move semantics.  It has a move() function that 
 transfers raw state between objects, and D structs are 
 supposed to be designed so they are movable by means of raw 
 bit transfer, allowing the compiler and GC to move them around 
 as it sees fit.  But maybe I'm missing something?
Well, but that is like saying that C++03 also had move semantics. There is nothing special about D's move(), it's just a library function?
What is special is D's requirement that structs be movable by a raw bit blit, which again enables our particular library implementation of move(). C++ has no such requirement; for example it is perfectly OK for an on-stack C++ object to contain a pointer to itself. A D-like move() on such an object would just produce mayhem.
 No? With D's std.move() the resource can be destroyed or get 
 into an inconsistent state if the caller does it wrong?
I guess this is what I don't understand. How and when does that happen?
The std.move() actually does a copy, then copy the init value to the original. If something happens that prevents the value from being preserved the object will be destroyed by the destructors. I.e. an exception.
If you are still talking about this case: fun(move(someResource)); where fun() throws, like Sönke I don't have a big problem with the resource being lost there. Given D's semantics, this is what I would expect. And in D, at least you *know* it is lost, and it is easy to understand and explain to users. In C++ you have no idea whether the resource is lost -- it depends on when the actual move operation happens and when the exception is thrown.
 And worse, if you have back pointers to it, it will end up 
 being inconsistent. There is no way the type system can prevent 
 back pointers without preventing D from being usable as a 
 language. Since you no longer have the original object... shit 
 can happen. In C++ you can set a mutex in the object and fix 
 things because you have the full object. So if someone tries to 
 follow the back pointer the mutex will block. You can probably 
 come up with many other scenarios. "postblit" does not fix this 
 (not a very elegant solution IMO).
Still not following you. Postblit is not involved in a move at all -- that's what makes it a move. If an exception is thrown, the object's destructor is called, and it would be the destructor's responsibility to break all ties to other objects.
 [...]
Feb 13 2016
parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= writes:
On Saturday, 13 February 2016 at 21:41:06 UTC, Lars T. 
Kyllingstad wrote:
 D is all about opinionated solutions. :)  In fact, I would go 
 so far as to say that's what sets D apart from C++, and mostly 
 in a good way.
D1 was all about opinionated ready-made builtin solutions, but D2 is supposedly meant to support generic programming and then the responsibility of "creating opinions" should be moved out of the language and into libraries.
 Whose expectations?  The formal expectation, as per the C++ 
 standard, is that the moved-from object be left in a "valid but 
 unspecified state".  Basically, as long as it is safe to 
 destroy or reassign to the moved-from object, you're good.
Hmm, do you have a reference (URL) to the "valid but unspecified state" part? I'm not quite sure what that refers to.
 I hope this is not coming across as me endorsing the practice 
 of implementing move assignment in terms of swap, because I 
 don't.  But it *is* a rather common practice, enough so that 
 Scott Meyers felt the need to write an article about it:
I've never heard of it, and never thought it would be a good idea. Are you sure this is common?
 A swap is three moves -- actual moves.
If you are talking std::swap, probably. Never use it, so don't know if there is any measurable overhead. If you are talking about the microcode in the CPU, then it typically takes 2 loads and 2 stores to swap two pointers on the heap, and the loads and stores can execute in parallel... So performance wise, not a big deal. But with debugging/consistency in mind you should set the source to nullptr instead. I know programmers talk alot about swap being implemented as tmp = a a = b b = a But that is actually how it is specified it source code, not how it is implemented in running code. In the CPU it goes like this: reg1 = load a; reg2 = load b b = store reg1; a = store reg2 Setting it to null would be almost the same. reg1 = load a; reg2 = 0 b = store reg1; a = store reg2 Unless you use special commands and zero out the entire cacheline of "a" you still get the same amount of cache-misses as well.
 What is special is D's requirement that structs be movable by a 
 raw bit blit, which again enables our particular library 
 implementation of move().

 C++ has no such requirement; for example it is perfectly OK for 
 an on-stack C++ object to contain a pointer to itself.  A 
 D-like move() on such an object would just produce mayhem.
Yes, but it isn't enforced by the compiler by static analysis, is it? So D has no particular advantage to C++ for an object that is designed to be movable.
 and it is easy to understand and explain to users.  In C++ you 
 have no idea whether the resource is lost -- it depends on when 
 the actual move operation happens and when the exception is 
 thrown.
Well, you do, if you implement exception safe RAII, which you should. RAII ensures that it will be released when the owner object is destructed.
 Still not following you.  Postblit is not involved in a move at 
 all -- that's what makes it a move.
Well, if you have a back pointer that is part of the invariant for the type, then neither move or copy work as expected. In C++ you have the address of the source object and can either modify or change the associated data-structure that provide back pointers (e.g. a global hash with pointers to the struct, in C++ you can change these pointers to point to the new location/add another entry). AFAIK this is not possible in D without adding an indirection.
Feb 13 2016
next sibling parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= writes:
On Saturday, 13 February 2016 at 22:42:34 UTC, Ola Fosheim 
Grøstad wrote:
 Hmm, do you have a reference (URL) to the "valid but 
 unspecified state" part? I'm not quite sure what that refers to.
Nevermind, I don't need a reference. I think what you refer to is just a statement that says that you can do whatever you want, but the destructor will run so the object must be in a valid state?
 tmp = a
 a = b
 b = a
Duh, "b=tmp"...
Feb 13 2016
prev sibling parent reply Lars T. Kyllingstad <public kyllingen.net> writes:
On Saturday, 13 February 2016 at 22:42:34 UTC, Ola Fosheim 
Grøstad wrote:
 On Saturday, 13 February 2016 at 21:41:06 UTC, Lars T. 
 Kyllingstad wrote:
 Whose expectations?  The formal expectation, as per the C++ 
 standard, is that the moved-from object be left in a "valid 
 but unspecified state".  Basically, as long as it is safe to 
 destroy or reassign to the moved-from object, you're good.
Hmm, do you have a reference (URL) to the "valid but unspecified state" part? I'm not quite sure what that refers to.
I know you said afterwards you didn't need a reference, but I'll give you one anyway. :) That is the formal requirement for C++ standard library types; see sec. 17.6.5.15 [lib.types.movedfrom] of the C++ specification. But I agree that, for the most part, one would expect that the moved-from object holds *no* resource and that the resource previously held by the target object has been released.
 I hope this is not coming across as me endorsing the practice 
 of implementing move assignment in terms of swap, because I 
 don't.  But it *is* a rather common practice, enough so that 
 Scott Meyers felt the need to write an article about it:
I've never heard of it, and never thought it would be a good idea. Are you sure this is common?
Pretty sure, but off the top of my head I can't give you too many concrete examples beyond the Meyers article I linked to, Stack Overflow questions, and one particular well-known and respected library (ZeroMQ) where I recently ran into it.
 A swap is three moves -- actual moves.
If you are talking std::swap, probably. Never use it, so don't know if there is any measurable overhead.
I was, yes.
 If you are talking about the microcode in the CPU, then it 
 typically takes 2 loads and 2 stores to swap two pointers on 
 the heap, and the loads and stores can execute in parallel... 
 So performance wise, not a big deal. But with 
 debugging/consistency in mind you should set the source to 
 nullptr instead.

 I know programmers talk alot about swap being implemented as

 tmp = a
 a = b
 b = a

 But that is actually how it is specified it source code, not 
 how it is implemented in running code. In the CPU it goes like 
 this:

 reg1 = load a; reg2 = load b
 b = store reg1; a = store reg2

 Setting it to null would be almost the same.

 reg1 = load a; reg2 = 0
 b = store reg1; a = store reg2

 Unless you use special commands and zero out the entire 
 cacheline of "a" you still get the same amount of cache-misses 
 as well.
For your lowest-level resource owners, I guess that is the case. But if a and b are largeish compound types that don't fit in a register, that's not the case, right? Or can the optimiser deal with this in a good way too?
 Well, if you have a back pointer that is part of the invariant 
 for the type, then neither move or copy work as expected. In 
 C++ you have the address of the source object and can either 
 modify or change the associated data-structure that provide 
 back pointers (e.g. a global hash with pointers to the struct, 
 in C++ you can change these pointers to point to the new 
 location/add another entry). AFAIK this is not possible in D 
 without adding an indirection.
So what you're saying that a particular kind of designs/patters can not be safely combined with D's standard move mechanism. That is of course very unfortunate, but I guess it can be worked around? I thought you were implying that simply using move() on any struct could potentially mess it up...
Feb 14 2016
parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= writes:
On Sunday, 14 February 2016 at 12:53:08 UTC, Lars T. Kyllingstad 
wrote:
 I know you said afterwards you didn't need a reference, but 
 I'll give you one anyway. :)  That is the formal requirement 
 for C++ standard library types; see sec. 17.6.5.15 
 [lib.types.movedfrom] of the C++ specification.
k thx. ;-) (I'll look at it later)
 For your lowest-level resource owners, I guess that is the 
 case.  But if a and b are largeish compound types that don't 
 fit in a register, that's not the case, right?  Or can the 
 optimiser deal with this in a good way too?
This is where measuring different compiler switch setups starts to matter, inlining and also cache effects... :-) I don't think the size matters, for swap, except for potential cache misses. As long as there are no barriers you can just swap field after field. If the loop is tight, then the loop is unrolled inside the CPU before the micro-ops are scheduled into the pipeline IIRC.
 That is of course very unfortunate, but I guess it can be 
 worked around?
You can work around it by having extra pointers/containers in or held by the struct (pointers to the source that is pointing to you). But that takes more space.
Feb 14 2016
parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= writes:
On Sunday, 14 February 2016 at 14:12:09 UTC, Ola Fosheim Grøstad 
wrote:
 You can work around it by having extra pointers/containers in 
 or held by the struct (pointers to the source that is pointing 
 to you). But that takes more space.
In the not-multithreaded version. In the multithreaded version you would have to use a heap allocated object that does not move and put the mutex there. If std.move(x) wipes out x, without checking mutexes first... well, that can't work well with multi-threading. I guess you could avoid using std.move(x) and use a different function, but how do you ensure that?
Feb 14 2016
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 02/03/2016 10:05 AM, Sönke Ludwig wrote:
 For std.move, isn't the only place where an exception can be thrown in
 the destructor (which shouldn't throw)? It uses memcpy to move the
 memory around to circumvent any extended construction logic.
Destructors in D are allowed to throw (thanks to exception chaining), but now I realize we need to amend that - the destructor of T.init should not be allowed to throw. -- Andrei
Feb 03 2016
parent reply Minas Mina <minas_0 hotmail.co.uk> writes:
On Wednesday, 3 February 2016 at 18:04:38 UTC, Andrei 
Alexandrescu wrote:
 On 02/03/2016 10:05 AM, Sönke Ludwig wrote:
 For std.move, isn't the only place where an exception can be 
 thrown in
 the destructor (which shouldn't throw)? It uses memcpy to move 
 the
 memory around to circumvent any extended construction logic.
Destructors in D are allowed to throw (thanks to exception chaining), but now I realize we need to amend that - the destructor of T.init should not be allowed to throw. -- Andrei
If this becomes the case, please make destructors nothrow so that people won't screw up (like they can very easily do in C++).
Feb 03 2016
parent David Nadlinger <code klickverbot.at> writes:
On Wednesday, 3 February 2016 at 19:18:12 UTC, Minas Mina wrote:
 If this becomes the case, please make destructors nothrow so 
 that people won't screw up (like they can very easily do in 
 C++).
This would be way too much of a restriction. Throwing in destructors is perfectly fine in D, there is nothing comparable to C++ where you can "screw up" in the sense that your program gets terminated when you throw an exception while another is already being unwound. — David
Feb 03 2016
prev sibling parent reply Matt Elkins <notreal fake.com> writes:
On Tuesday, 2 February 2016 at 22:36:22 UTC, Ali Çehreli wrote:
 Have you used something similar before? Is this a correct 
 approach to this problem?
This reminds me of C++ prior to C++11; there were libraries with whole sets of data structures intended to make move-like semantics work. While functional (C++ certainly did fine with it for many years), it's not ideal. That said, I don't know a better solution. For my resource handles, I am expanding the array and then assigning the handle in to the new slots, but that's only acceptable because I happen to know my assignments are cheap. And even then, it is a bit ugly. Thus far in my (admittedly short) explorations of D, this has been my only major gripe. It feels like a significant step down from C++, but that significance is probably unique to my use case -- the particular project I am working with happens to use a lot of non-memory, non-copyable (but movable) resources. In some of the projects I do at work, I would hardly notice this. This [apparent] lack of clean move semantics is one of only a handful of things keeping me from wholeheartedly converting to D, and proselytizing the gospel to my coworkers (most of the other issues are transient, like compiler bugs). Everything else in D has been pretty awesome so far, and I definitely plan on continuing to use it for my pet projects for the time being :). Anecdote: just after porting one of my C++ classes to D, I realized that without sacrificing generality, performance, or readability I had cut away something like 1/3 of the code, primarily from the cleaner syntax (this actually increased readability). I know D isn't _just_ "cleaner C++", but it sure is cleaner than C++!
Feb 03 2016
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 02/03/2016 07:48 PM, Matt Elkins wrote:
 This [apparent] lack of clean move semantics
I very much wish there was a quick summary. I figure you've seen std.algorithm.move. What's missing? -- Andrei
Feb 03 2016
next sibling parent reply Matt Elkins <notreal fake.com> writes:
On Thursday, 4 February 2016 at 01:26:55 UTC, Andrei Alexandrescu 
wrote:
 On 02/03/2016 07:48 PM, Matt Elkins wrote:
 This [apparent] lack of clean move semantics
I very much wish there was a quick summary. I figure you've seen std.algorithm.move. What's missing? -- Andrei
Apologies, should have summarized. The issue I've been focused on has been emulating C++'s std::unique_ptr for the purposes of handling system resources which need explicit cleanup and which are not copyable (or for which I want to disallow copying for whatever reason). I want to still be able to move around my handles to these resources, so long as the single-owner aspect is maintained, and I want the wrapper to have minimal overhead (so for example std.typecons.Unique is out, since it appears to require the heap one way or another); in essence, I want std::unique_ptr. It is quite possible that this is doable, but if so the solution eludes me. Here is what I have so far: [code] import std.algorithm; struct ResourceHandle(T, alias Deleter, T Default = T.init) { // Constructors/Destructor this(T handle) {m_handle = handle;} disable this(this); ~this() {Deleter(m_handle);} // Operators disable void opAssign(ref ResourceHandle lvalue); ref ResourceHandle opAssign(ResourceHandle rvalue) {swap(m_handle, rvalue.m_handle); return this;} // Methods property inout(T) handle() inout {return m_handle;} property T handle(T handle) {Deleter(m_handle); m_handle = handle; return m_handle;} T release() {T result = m_handle; m_handle = Default; return result;} private: T m_handle = Default; } [/code] This seems to cover most of my bases, but I still can't do things like this: [code] unittest { alias RH = ResourceHandle!(uint, (uint) {}); RH[] handles; handles ~= RH(5); // Compile error: ResourceHandle is not copyable because it is annotated with disable } [/code] One person on the forums suggested that this might be a compiler bug (and referred me to a maybe-related bug report from 2011), but it also might not. This seems to have similar practical functionality to std::auto_ptr; works reasonably well for passing around individual instances, but gets iffy when you want to put it into a container. Incidentally, I -am- able to do this: [code] unittest { alias RH = ResourceHandle!(uint, (uint) {}); RH[] handles; ++handles.length; handles[$ - 1] = RH(5); } [/code] This has become my effective workaround, but it isn't terribly clean and may not scale to other kinds of data structures (or it may, haven't thought it through). Of course, I'm a D newbie and could well just not be seeing the right way to define ResourceHandle. Sorry for all the C++ allusions to explain my intent. Then again, I'm not terribly worried that the author of MC++D won't follow them :).
Feb 03 2016
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 02/03/2016 09:01 PM, Matt Elkins wrote:
 [code]
 import std.algorithm;

 struct ResourceHandle(T, alias Deleter, T Default = T.init)
 {
      // Constructors/Destructor
      this(T handle) {m_handle = handle;}
       disable this(this);
      ~this() {Deleter(m_handle);}

      // Operators
       disable void opAssign(ref ResourceHandle lvalue);
      ref ResourceHandle opAssign(ResourceHandle rvalue) {swap(m_handle,
 rvalue.m_handle); return this;}

      // Methods
       property inout(T) handle() inout {return m_handle;}
       property T handle(T handle) {Deleter(m_handle); m_handle = handle;
 return m_handle;}
      T release() {T result = m_handle; m_handle = Default; return result;}

      private:
          T m_handle = Default;
 }
 [/code]

 This seems to cover most of my bases, but I still can't do things like
 this:

 [code]
 unittest
 {
      alias RH = ResourceHandle!(uint, (uint) {});
      RH[] handles;
      handles ~= RH(5); // Compile error: ResourceHandle is not copyable
 because it is annotated with  disable
 }
 [/code]
Got it, thanks. That's a bug in the implementation, no two ways about it. No copy should occur there, neither theoretically nor practically. Please report it to bugzilla at http://issues.dlang.org. Thanks very much! -- Andrei
Feb 03 2016
next sibling parent rsw0x <anonymous anonymous.com> writes:
On Thursday, 4 February 2016 at 02:33:06 UTC, Andrei Alexandrescu 
wrote:
 On 02/03/2016 09:01 PM, Matt Elkins wrote:
 [...]
Got it, thanks. That's a bug in the implementation, no two ways about it. No copy should occur there, neither theoretically nor practically. Please report it to bugzilla at http://issues.dlang.org. Thanks very much! -- Andrei
possibly related https://issues.dlang.org/show_bug.cgi?id=7579
Feb 03 2016
prev sibling next sibling parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= writes:
On Thursday, 4 February 2016 at 02:33:06 UTC, Andrei Alexandrescu 
wrote:
 Got it, thanks. That's a bug in the implementation, no two ways 
 about it. No copy should occur there, neither theoretically nor 
 practically. Please report it to bugzilla at 
 http://issues.dlang.org. Thanks very much! -- Andrei
I don't see how that point can be argued. The language reference states: « a op= b are semantically equivalent to: a = cast(typeof(a))(a op b) » Therefore: «a ~= b» should be semantically equivalent to « a = cast(typeof(a))(a ~ b)» So if the language reference is correct you need copy semantics? I hope you guys will make sure that the language reference is in sync with the implementation.
Feb 04 2016
prev sibling next sibling parent Era Scarecrow <rtcvb32 yahoo.com> writes:
On Thursday, 4 February 2016 at 02:33:06 UTC, Andrei Alexandrescu 
wrote:
 On 02/03/2016 09:01 PM, Matt Elkins wrote:
 [code]
 unittest
 {
      alias RH = ResourceHandle!(uint, (uint) {});
      RH[] handles;
      handles ~= RH(5); // Compile error: ResourceHandle is not 
 copyable
 because it is annotated with  disable
 }
 [/code]
Got it, thanks. That's a bug in the implementation, no two ways about it. No copy should occur there, neither theoretically nor practically. Please report it to bugzilla at http://issues.dlang.org. Thanks very much! -- Andrei
Something like a week ago i sorta was awaiting confirmation of a bug VERY similar to this one, from this thread posting: http://forum.dlang.org/post/iahbnmcbtsdolczaqkta forum.dlang.org The related bug reported (not a exact match, but close enough) is also present https://issues.dlang.org/show_bug.cgi?id=7032
Feb 04 2016
prev sibling parent reply Matt Elkins <notreal fake.com> writes:
On Thursday, 4 February 2016 at 02:33:06 UTC, Andrei Alexandrescu 
wrote:
 Got it, thanks. That's a bug in the implementation, no two ways 
 about it. No copy should occur there, neither theoretically nor 
 practically. Please report it to bugzilla at 
 http://issues.dlang.org. Thanks very much! -- Andrei
Done (sorry for the delay): https://issues.dlang.org/show_bug.cgi?id=15662
Feb 08 2016
next sibling parent reply Hara Kenji <k.hara.pg gmail.com> writes:
On Tuesday, 9 February 2016 at 00:25:33 UTC, Matt Elkins wrote:
 On Thursday, 4 February 2016 at 02:33:06 UTC, Andrei 
 Alexandrescu wrote:
 Got it, thanks. That's a bug in the implementation, no two 
 ways about it. No copy should occur there, neither 
 theoretically nor practically. Please report it to bugzilla at 
 http://issues.dlang.org. Thanks very much! -- Andrei
Done (sorry for the delay): https://issues.dlang.org/show_bug.cgi?id=15662
I've replied to issue 15662. The point of that issue is, an array concatenation or appending may cause the elements' copy. The copy occurrence is determined by the runtime situation, so compiler needs to reject such the operation for the disable this(this) elements conservatively. Kenji Hara
Feb 09 2016
next sibling parent Matt Elkins <notreal fake.com> writes:
On Tuesday, 9 February 2016 at 13:45:13 UTC, Hara Kenji wrote:
 On Tuesday, 9 February 2016 at 00:25:33 UTC, Matt Elkins wrote:
 On Thursday, 4 February 2016 at 02:33:06 UTC, Andrei 
 Alexandrescu wrote:
 Got it, thanks. That's a bug in the implementation, no two 
 ways about it. No copy should occur there, neither 
 theoretically nor practically. Please report it to bugzilla 
 at http://issues.dlang.org. Thanks very much! -- Andrei
Done (sorry for the delay): https://issues.dlang.org/show_bug.cgi?id=15662
I've replied to issue 15662. The point of that issue is, an array concatenation or appending may cause the elements' copy. The copy occurrence is determined by the runtime situation, so compiler needs to reject such the operation for the disable this(this) elements conservatively. Kenji Hara
I see. Unfortunate. But thanks for the explanation!
Feb 09 2016
prev sibling parent Matt Elkins <notreal fake.com> writes:
On Tuesday, 9 February 2016 at 13:45:13 UTC, Hara Kenji wrote:
 On Tuesday, 9 February 2016 at 00:25:33 UTC, Matt Elkins wrote:
 On Thursday, 4 February 2016 at 02:33:06 UTC, Andrei 
 Alexandrescu wrote:
 Got it, thanks. That's a bug in the implementation, no two 
 ways about it. No copy should occur there, neither 
 theoretically nor practically. Please report it to bugzilla 
 at http://issues.dlang.org. Thanks very much! -- Andrei
Done (sorry for the delay): https://issues.dlang.org/show_bug.cgi?id=15662
I've replied to issue 15662. The point of that issue is, an array concatenation or appending may cause the elements' copy. The copy occurrence is determined by the runtime situation, so compiler needs to reject such the operation for the disable this(this) elements conservatively. Kenji Hara
Actually, I have a follow-up question about this: when you say that an array concatenation may cause the elements' copy, do you mean only the element being appended? Or can elements already in the array be copied? If the latter, isn't it a problem that my workaround for appending non-copyable objects into the array compiles? My workaround is like this (typing directly without testing): // Foo has opAssign defined for rvalues, but post-blit and opAssign for lvalues disabled Foo[] fooArray; ++fooArray.length; fooArray[$ - 1] = Foo();
Feb 10 2016
prev sibling next sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/8/16 7:25 PM, Matt Elkins wrote:
 On Thursday, 4 February 2016 at 02:33:06 UTC, Andrei Alexandrescu wrote:
 Got it, thanks. That's a bug in the implementation, no two ways about
 it. No copy should occur there, neither theoretically nor practically.
 Please report it to bugzilla at http://issues.dlang.org. Thanks very
 much! -- Andrei
Done (sorry for the delay): https://issues.dlang.org/show_bug.cgi?id=15662
Thx! -- Andrei
Feb 09 2016
prev sibling parent Era Scarecrow <rtcvb32 yahoo.com> writes:
On Tuesday, 9 February 2016 at 00:25:33 UTC, Matt Elkins wrote:
 Done (sorry for the delay): 
 https://issues.dlang.org/show_bug.cgi?id=15662
I'll have to reiterate that this is closely related to another bug (or maybe the same thing, just a lot more compact). https://issues.dlang.org/show_bug.cgi?id=7032 Bug 7032 refers to opAssign being defined (and being ignored) due to the postblit. A simpler test to what effectively is the same problem is just disabling the postblit (and nothing else). [code] struct S { disable this(this); } S[] s; s ~= S(); //error, is not copyable because it is annotated with disable [/code] I currently can't seem to register/reset/login to bugzilla so i can't post this there :( Could be a change with the site since i haven't been there in 2 years...
Feb 09 2016
prev sibling parent reply maik klein <maikklein googlemail.com> writes:
On Thursday, 4 February 2016 at 01:26:55 UTC, Andrei Alexandrescu 
wrote:
 On 02/03/2016 07:48 PM, Matt Elkins wrote:
 This [apparent] lack of clean move semantics
I very much wish there was a quick summary. I figure you've seen std.algorithm.move. What's missing? -- Andrei
I am in a similar boat as Matt Elkins. The problem is not D's move semantics, but that Phobos does not support them at all. I have already several things that are not copyable, one of them is "Unique". It feels incredibly restrictive to use those in D. You can not put them in Arrays, you can not put them in Tuples, you can not use writeln on them, you can basically not use them at all in Phobos because pretty much everything relies on copying. I have currently written tons of stuff from scratch just to support a few non copyable types. This is probably my biggest complain about D so far. It might still be the case that I am missing something obvious here.
Feb 03 2016
next sibling parent reply rsw0x <anonymous anonymous.com> writes:
On Thursday, 4 February 2016 at 03:45:57 UTC, maik klein wrote:
 On Thursday, 4 February 2016 at 01:26:55 UTC, Andrei 
 Alexandrescu wrote:
 [...]
I am in a similar boat as Matt Elkins. The problem is not D's move semantics, but that Phobos does not support them at all. I have already several things that are not copyable, one of them is "Unique". It feels incredibly restrictive to use those in D. You can not put them in Arrays, you can not put them in Tuples, you can not use writeln on them, you can basically not use them at all in Phobos because pretty much everything relies on copying. I have currently written tons of stuff from scratch just to support a few non copyable types. This is probably my biggest complain about D so far. It might still be the case that I am missing something obvious here.
Those are intended not to be copied, they must be explicitly moved with std.algorithm's move
Feb 03 2016
parent reply maik klein <maikklein googlemail.com> writes:
On Thursday, 4 February 2016 at 03:52:23 UTC, rsw0x wrote:
 On Thursday, 4 February 2016 at 03:45:57 UTC, maik klein wrote:
 On Thursday, 4 February 2016 at 01:26:55 UTC, Andrei 
 Alexandrescu wrote:
 [...]
I am in a similar boat as Matt Elkins. The problem is not D's move semantics, but that Phobos does not support them at all. I have already several things that are not copyable, one of them is "Unique". It feels incredibly restrictive to use those in D. You can not put them in Arrays, you can not put them in Tuples, you can not use writeln on them, you can basically not use them at all in Phobos because pretty much everything relies on copying. I have currently written tons of stuff from scratch just to support a few non copyable types. This is probably my biggest complain about D so far. It might still be the case that I am missing something obvious here.
Those are intended not to be copied, they must be explicitly moved with std.algorithm's move
I don't understand what you are saying. Are you saying that I can use them with Arrays, Tuples etc and I just have to use move somehow? If that is the case please show me because I have already wasted a lot of time into recreating Array, Tuple etc. Or are you saying that I they are not compatible with Phobos?
Feb 03 2016
parent rsw0x <anonymous anonymous.com> writes:
On Thursday, 4 February 2016 at 03:57:18 UTC, maik klein wrote:
 On Thursday, 4 February 2016 at 03:52:23 UTC, rsw0x wrote:
 On Thursday, 4 February 2016 at 03:45:57 UTC, maik klein wrote:
 [...]
Those are intended not to be copied, they must be explicitly moved with std.algorithm's move
I don't understand what you are saying. Are you saying that I can use them with Arrays, Tuples etc and I just have to use move somehow? If that is the case please show me because I have already wasted a lot of time into recreating Array, Tuple etc. Or are you saying that I they are not compatible with Phobos?
Can you show me an example of the issue you're having? You might be hitting the same bug as already seen in this thread. Theoretically they should be workable with move. ...Theoretically : )
Feb 03 2016
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 02/03/2016 10:45 PM, maik klein wrote:
 It might still be the case that I am missing something obvious here.
You're not. The situation with noncopyable types is similar to that in C++ pre-11: people could define them, but the stdlib implementation wasn't supporting them in all places possible. This is not difficult - all we need is to figure which library primitives should support noncopyable types and change implementations accordingly. Andrei
Feb 03 2016
prev sibling parent reply w0rp <devw0rp gmail.com> writes:
Back on the original topic, Scott Meyers often says "std::move 
doesn't move." It's more like std::rvalue_cast. C++ uses r-value 
references in order to be able to rip the guts out of objects and 
put them into other objects.

D doesn't have a distinct r-value reference type, and postblit is 
part of the struct types. If you write simply T foo(); and you 
return some struct T, D already moves the struct out of the 
function without you having to define any move constructors. That 
kind of return value optimisation was the original motivation for 
r-value references, for when C++98 RVO isn't good enough, from my 
understanding.

The only remaining time you need to avoid copies is when you take 
something already on the stack, and then put it into some other 
object, into some collection, etc. That's the other power that 
std::move affords you. The move functions in std.algorithm should 
take care of that.

Maybe someone else will correct me on a point or two there, but 
that's the understanding of move semantics in D that I have had.
Feb 10 2016
next sibling parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= writes:
On Wednesday, 10 February 2016 at 20:42:29 UTC, w0rp wrote:
 Back on the original topic, Scott Meyers often says "std::move 
 doesn't move." It's more like std::rvalue_cast. C++ uses 
 r-value references in order to be able to rip the guts out of 
 objects and put them into other objects.
Well. In C++ "std::move(x)" is just a "static_cast<T&&>(x)" for "T x". "T&&" references are different from "T&" by acting like T& references when used, but not on overloads. They are primarily for distinguishing between overloads on temporaries in function calls, T&& binds to temporaries. So you use "std::move(x)" to tell the type system that you want it to be cast as a references to a temporary like reference (or rvalue reference). So that's why constructors with "T&&" are called move constructors (taking stuff from temporaries) and "const T&" are called copy constructors (assuming that the parameter might have a long life on it's own).
 That kind of return value optimisation was the original 
 motivation for r-value references, for when C++98 RVO isn't 
 good enough, from my understanding.
It is for overloading. Why allocate lots of stuff by copying it if you know that the referenced object is about to die anyway? If it is dying we just steal the stuff it is holding. stack.push(string("hiii")) // we could steal this stuff string x("hiii") stack.push(x) // we have to copy this stuff
 Maybe someone else will correct me on a point or two there, but 
 that's the understanding of move semantics in D that I have had.
I don't think D has move semantics... It does copying and clearing... The postblit thing looks like a dirty hack to me.
Feb 10 2016
parent reply Atila Neves <atila.neves gmail.com> writes:
On Wednesday, 10 February 2016 at 22:32:54 UTC, Ola Fosheim 
Grøstad wrote:
 On Wednesday, 10 February 2016 at 20:42:29 UTC, w0rp wrote:
 Back on the original topic, Scott Meyers often says "std::move 
 doesn't move." It's more like std::rvalue_cast. C++ uses 
 r-value references in order to be able to rip the guts out of 
 objects and put them into other objects.
Well. In C++ "std::move(x)" is just a "static_cast<T&&>(x)" for "T x". "T&&" references are different from "T&" by acting like T& references when used, but not on overloads. They are primarily for distinguishing between overloads on temporaries in function calls, T&& binds to temporaries. So you use "std::move(x)" to tell the type system that you want it to be cast as a references to a temporary like reference (or rvalue reference). So that's why constructors with "T&&" are called move constructors (taking stuff from temporaries) and "const T&" are called copy constructors (assuming that the parameter might have a long life on it's own).
 That kind of return value optimisation was the original 
 motivation for r-value references, for when C++98 RVO isn't 
 good enough, from my understanding.
It is for overloading. Why allocate lots of stuff by copying it if you know that the referenced object is about to die anyway? If it is dying we just steal the stuff it is holding. stack.push(string("hiii")) // we could steal this stuff string x("hiii") stack.push(x) // we have to copy this stuff
 Maybe someone else will correct me on a point or two there, 
 but that's the understanding of move semantics in D that I 
 have had.
I don't think D has move semantics... It does copying and clearing... The postblit thing looks like a dirty hack to me.
D has move semantics. Deep copies are done with post-blit. Fair enough if you just: auto foo = bar; Then it's a shallow copy. The only difference to a "true" move is that bar isn't T.init, but that's easily done with the move function (assuming the struct has a destructor) or manually. C++: void foo(Foo); //copy void foo(Foo&); //by-ref, only lvalues void foo(Foo&&); //move, only rvalues D: void foo(Foo); //copies copiable types, moves non-copiable ones void foo(ref Foo); //by-ref, only lvalues In D, the foo(Foo) variant can be called with lvalues as long as they don't disable this(this). Any type that does isn't copiable so you can only pass rvalues in. Atila
Feb 11 2016
parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= writes:
On Thursday, 11 February 2016 at 14:25:39 UTC, Atila Neves wrote:
 D has move semantics. Deep copies are done with post-blit. Fair 
 enough if you just:

 auto foo = bar;

 Then it's a shallow copy. The only difference to a "true" move 
 is that bar isn't T.init, but that's easily done with the move 
 function (assuming the struct has a destructor) or manually.
*blank stare*
 C++:

 void foo(Foo); //copy
 void foo(Foo&); //by-ref, only lvalues
 void foo(Foo&&); //move, only rvalues
In modern generics-oriented C++ I would say: void foo(T); // by value - and probably not what you want void foo(const T&) // copy semantics overload void foo(T&&) // move semantics overload Please keep in mind that C++ do perfect forwarding of those rvalue references when you pass it down a call chain.
Feb 11 2016
parent reply Atila Neves <atila.neves gmail.com> writes:
On Thursday, 11 February 2016 at 14:38:24 UTC, Ola Fosheim 
Grøstad wrote:
 On Thursday, 11 February 2016 at 14:25:39 UTC, Atila Neves 
 wrote:
 D has move semantics. Deep copies are done with post-blit. 
 Fair enough if you just:

 auto foo = bar;

 Then it's a shallow copy. The only difference to a "true" move 
 is that bar isn't T.init, but that's easily done with the move 
 function (assuming the struct has a destructor) or manually.
*blank stare*
Err... ok.
 C++:

 void foo(Foo); //copy
 void foo(Foo&); //by-ref, only lvalues
 void foo(Foo&&); //move, only rvalues
In modern generics-oriented C++ I would say: void foo(T); // by value - and probably not what you want
It depends. For small structs, it is. And in some cases the compiler can elide the copy.
 void foo(const T&) // copy semantics overload
 void foo(T&&) // move semantics overload
I forgot the const. It doesn't change my point.
 Please keep in mind that C++ do perfect forwarding of those 
 rvalue references when you pass it down a call chain.
No it doesn't. It _allows_ you to perfect forward, as long as you remember to use `std::forward`. And in that case, they're not really rvalue references, they're forwarding references (what Scott Meyers initially called universal references). The only issue that I know of with D's approach is that, if you want to pass by ref for efficiency reasons, then you can't pass an rvalue in. It's never been a problem for me. Atila
Feb 11 2016
parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= writes:
On Thursday, 11 February 2016 at 16:31:03 UTC, Atila Neves wrote:
 On Thursday, 11 February 2016 at 14:38:24 UTC, Ola Fosheim 
 Grøstad wrote:
 On Thursday, 11 February 2016 at 14:25:39 UTC, Atila Neves 
 wrote:
 D has move semantics. Deep copies are done with post-blit. 
 Fair enough if you just:

 auto foo = bar;

 Then it's a shallow copy. The only difference to a "true" 
 move is that bar isn't T.init, but that's easily done with 
 the move function (assuming the struct has a destructor) or 
 manually.
*blank stare*
Err... ok.
I don't see how D's parameter semantics can be called move semantics, when you essentially can emulate it in C++ without using C++'s move semantics?
 void foo(const T&) // copy semantics overload
 void foo(T&&) // move semantics overload
I forgot the const. It doesn't change my point.
The point is of course that you use (const T&) instead of copying, so you don't have to deal with the constructor/destructor overhead?
 No it doesn't. It _allows_ you to perfect forward, as long as 
 you remember to use `std::forward`. And in that case, they're 
 not really rvalue references, they're forwarding references 
 (what Scott Meyers initially called universal references).
Of course you have to use std::forward, that follows from what I said further up about how "T&&" parameters act when used.
Feb 11 2016
prev sibling parent reply Matt Elkins <notreal fake.com> writes:
On Wednesday, 10 February 2016 at 20:42:29 UTC, w0rp wrote:
 The only remaining time you need to avoid copies is when you 
 take something already on the stack, and then put it into some 
 other object, into some collection, etc. That's the other power 
 that std::move affords you. The move functions in std.algorithm 
 should take care of that.

 Maybe someone else will correct me on a point or two there, but 
 that's the understanding of move semantics in D that I have had.
Maybe this is what you are referring to, but the primary use I get out of move semantics (in general, not language-specific) has little to do with performance-on-copy. It is for handling resources which logically aren't copyable, which have a unique owner at all times and which should be cleaned up as soon as unique owner falls out of scope. This situation occurs a lot for me, and RAII plus move semantics are pretty close to ideal for handling it. Yes, it can be approximated with reference counting, but reference counting has its own downsides. C++11 definitely supports this use case. I -think- that D does as well, possible compiler issues aside, though I haven't used it as extensively here to be sure yet. It does seem as though one has to be more careful using these semantics in D, because of how easy it is to stuff such an object into a GC-based container and thereby lose the deterministic nature of the cleanup, but that's just more something to be aware of than a true limitation.
Feb 10 2016
next sibling parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= writes:
On Thursday, 11 February 2016 at 00:32:11 UTC, Matt Elkins wrote:
 unique owner falls out of scope. This situation occurs a lot 
 for me, and RAII plus move semantics are pretty close to ideal 
 for handling it. Yes, it can be approximated with reference 
 counting, but reference counting has its own downsides.
C++ unique_ptr is a semantically a reference-counting ptr with a max count of 1. So, when you are using a shared_ptr, you also use move semantics. Meaning, you can transfer one shared_ptr to another without the overhead which would be a "move", or you can "copy" by increasing the count by one.
 C++11 definitely supports this use case. I -think- that D does 
 as well, possible compiler issues aside, though I haven't used 
 it as extensively here to be sure yet.
In C++ it is a type system issue, and the actual semantics are up to the programmer. In D it is just copy and clear, which does extra work and is less flexible _and_ forces the copying to happen so you cannot escape it. In C++ the type system tells the class what it _may_ do, not what has already happened (which is what D does). In C++ you aren't required to move, you can choose to use a different strategy/semantics. Like, in C++ you could do: "take_one_but_not_both(std::move(a),std::move(b))" That would not work in D.
Feb 10 2016
parent reply Matt Elkins <notreal fake.com> writes:
On Thursday, 11 February 2016 at 00:54:21 UTC, Ola Fosheim 
Grøstad wrote:
 On Thursday, 11 February 2016 at 00:32:11 UTC, Matt Elkins 
 wrote:
 unique owner falls out of scope. This situation occurs a lot 
 for me, and RAII plus move semantics are pretty close to ideal 
 for handling it. Yes, it can be approximated with reference 
 counting, but reference counting has its own downsides.
C++ unique_ptr is a semantically a reference-counting ptr with a max count of 1.
True, but with unique_ptr the max count is enforced by the compiler and can only be subverted by a programmer explicitly choosing to do so -- if that is possible with normal reference counting, I don't know of a way. Moreover, there is no heap allocation required which may or may not matter for a given use case. Of course there are ways to avoid or mitigate heap allocations for reference-counted pointers, but the point is that unique_ptr has the next best thing to no overhead at all, which allows it to be used in a broader range of contexts.
 In C++ it is a type system issue, and the actual semantics are 
 up to the programmer. In
 D it is just copy and clear, which does extra work and is less 
 flexible _and_ forces
 the copying to happen so you cannot escape it.
Fair point.
Feb 10 2016
parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= writes:
On Thursday, 11 February 2016 at 01:45:32 UTC, Matt Elkins wrote:
 True, but with unique_ptr the max count is enforced by the 
 compiler and can only be subverted by a programmer explicitly 
 choosing to do so -- if that is possible with normal reference 
 counting, I don't know of a way.
You "subvert" both unique_ptr and shared_ptr by taking the reference of the object and using it directly... Like a borrowed reference in Rust, except it is unchecked (by the compiler).
 Moreover, there is no heap allocation required which may or may 
 not matter for a given use case. Of course there are ways to 
 avoid or mitigate heap allocations for reference-counted 
 pointers, but the point is that unique_ptr has the next best 
 thing to no overhead at all, which allows it to be used in a 
 broader range of contexts.
Yes, I agree, I don't think reference counting is all that important. Although I tend to favour embedding objects rather than using unique_ptr... C++ supports it, but in a rather clunky way. I'd like to see a language that does that really well (sensible initialization tracking of embedded or global objects, so that you can safely delay initialization without resorting to using pointers). I never really use shared_ptr, but it is quite rich. AFAIK you can swap one pointer for another, you can swap the underlying object without touching the pointers, you can use weak_ptr which checks the presence of the object. I think shared_ptr is a good thing to have available, but you generally don't have to resort to using it. With a properly structured program you can get a long way with just unique_ptr and your own tailored made structs.
Feb 11 2016
prev sibling next sibling parent Atila Neves <atila.neves gmail.com> writes:
On Thursday, 11 February 2016 at 00:32:11 UTC, Matt Elkins wrote:
 On Wednesday, 10 February 2016 at 20:42:29 UTC, w0rp wrote:
 [...]
Maybe this is what you are referring to, but the primary use I get out of move semantics (in general, not language-specific) has little to do with performance-on-copy. It is for handling resources which logically aren't copyable, which have a unique owner at all times and which should be cleaned up as soon as unique owner falls out of scope. This situation occurs a lot for me, and RAII plus move semantics are pretty close to ideal for handling it. Yes, it can be approximated with reference counting, but reference counting has its own downsides. [...]
disable this(this) should be enough, no? Atila
Feb 11 2016
prev sibling parent Lars T. Kyllingstad <public kyllingen.net> writes:
On Thursday, 11 February 2016 at 00:32:11 UTC, Matt Elkins wrote:
 Maybe this is what you are referring to, but the primary use I 
 get out of move semantics (in general, not language-specific) 
 has little to do with performance-on-copy. It is for handling 
 resources which logically aren't copyable, which have a unique 
 owner at all times and which should be cleaned up as soon as 
 unique owner falls out of scope. This situation occurs a lot 
 for me, and RAII plus move semantics are pretty close to ideal 
 for handling it. Yes, it can be approximated with reference 
 counting, but reference counting has its own downsides.
This is my primary use case for move semantics too, in both C++ and D. Using noncopyable structs for this is perfect, because you can keep them on the stack and avoid heap allocation and indirection if you don't need to move them around a lot. If you do move them often enough that D's copy-and-clear semantics become a performance issue, just stick them in something like std.typecons.Unique or std::unique_ptr. Finally, if you need to have multiple owners/references, put them in a std.typecons.RefCounted or std::shared_ptr. Best of all worlds. Lars
Feb 13 2016