digitalmars.D - D's equivalent to C++'s std::move?
- Shachar Shemesh (11/11) Feb 01 2016 Hi all,
- Rikki Cattermole (24/35) Feb 01 2016 So just to confirm, you want to explicitly copy a struct but not
- =?UTF-8?Q?S=c3=b6nke_Ludwig?= (3/14) Feb 01 2016 Should work with move() from std.algorithm:
- Dicebot (23/30) Feb 01 2016 auto move (T) (ref T origin)
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (6/8) Feb 01 2016 Note that C++ std::move(...) doesn't do anything related to
- maik klein (4/14) Feb 02 2016 Could you share this? I would be very interested in how you have
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (15/17) Feb 02 2016 My ad hoc pointer experiments try to be general and is
- =?UTF-8?Q?Ali_=c3=87ehreli?= (185/194) Feb 02 2016 This question has been brought up a lot lately. I've decided to look at
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (7/9) Feb 02 2016 Nice to see that others are playing around with this, I don't
- =?UTF-8?Q?Ali_=c3=87ehreli?= (10/19) Feb 02 2016 Exactly. I've saved my rear end by inserting a TODO comment just before
- =?UTF-8?Q?S=c3=b6nke_Ludwig?= (4/26) Feb 03 2016 For std.move, isn't the only place where an exception can be thrown in
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (15/19) Feb 03 2016 Not sure if you are talking about something else, but in C++ if
- =?UTF-8?Q?S=c3=b6nke_Ludwig?= (8/25) Feb 03 2016 Hmm, that's true, it would get destroyed and you'd have to let
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (6/9) Feb 03 2016 Well, you can always move it back or wait with the move.
- =?UTF-8?Q?S=c3=b6nke_Ludwig?= (6/14) Feb 03 2016 That's probably indeed true since it relies on memcpy. You can of course...
- Lars T. Kyllingstad (23/33) Feb 13 2016 In my experience, in the vast majority of cases a C++ move
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (18/36) Feb 13 2016 No? Not in the libraries I write. A move typically just means
- Lars T. Kyllingstad (49/84) Feb 13 2016 Not knowing anything about the libraries you write, it's hard to
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (37/61) Feb 13 2016 C++ does indeed put the burden on the library programmer and is
- rsw0x (7/17) Feb 13 2016 D "guarantees" NRVO which is what enables its move semantics, C++
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (6/11) Feb 13 2016 By NRVO I assume you mean:
- rsw0x (7/19) Feb 13 2016 It's not move semantics, it enables move semantics.
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (13/17) Feb 13 2016 I understand what you mean. You mean construction of read/write
- Lars T. Kyllingstad (34/82) Feb 13 2016 D is all about opinionated solutions. :) In fact, I would go so
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (44/68) Feb 13 2016 D1 was all about opinionated ready-made builtin solutions, but D2
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (6/11) Feb 13 2016 Nevermind, I don't need a reference. I think what you refer to is
- Lars T. Kyllingstad (23/69) Feb 14 2016 I know you said afterwards you didn't need a reference, but I'll
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (13/23) Feb 14 2016 k thx. ;-) (I'll look at it later)
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (9/12) Feb 14 2016 In the not-multithreaded version. In the multithreaded version
- Andrei Alexandrescu (4/7) Feb 03 2016 Destructors in D are allowed to throw (thanks to exception chaining),
- Minas Mina (4/13) Feb 03 2016 If this becomes the case, please make destructors nothrow so that
- David Nadlinger (7/10) Feb 03 2016 This would be way too much of a restriction. Throwing in
- Matt Elkins (27/29) Feb 03 2016 This reminds me of C++ prior to C++11; there were libraries with
- Andrei Alexandrescu (3/4) Feb 03 2016 I very much wish there was a quick summary. I figure you've seen
- Matt Elkins (69/73) Feb 03 2016 Apologies, should have summarized. The issue I've been focused on
- Andrei Alexandrescu (5/37) Feb 03 2016 Got it, thanks. That's a bug in the implementation, no two ways about
- rsw0x (4/10) Feb 03 2016 possibly related
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (15/19) Feb 04 2016 I don't see how that point can be argued.
- Era Scarecrow (8/23) Feb 04 2016 Something like a week ago i sorta was awaiting confirmation of a
- Matt Elkins (4/8) Feb 08 2016 Done (sorry for the delay):
- Hara Kenji (8/16) Feb 09 2016 I've replied to issue 15662.
- Matt Elkins (2/19) Feb 09 2016 I see. Unfortunate. But thanks for the explanation!
- Matt Elkins (13/30) Feb 10 2016 Actually, I have a follow-up question about this: when you say
- Andrei Alexandrescu (2/8) Feb 09 2016 Thx! -- Andrei
- Era Scarecrow (18/20) Feb 09 2016 I'll have to reiterate that this is closely related to another
- maik klein (14/18) Feb 03 2016 I am in a similar boat as Matt Elkins. The problem is not D's
- rsw0x (3/19) Feb 03 2016 Those are intended not to be copied, they must be explicitly
- maik klein (6/29) Feb 03 2016 I don't understand what you are saying. Are you saying that I can
- rsw0x (5/16) Feb 03 2016 Can you show me an example of the issue you're having? You might
- Andrei Alexandrescu (8/9) Feb 03 2016 You're not. The situation with noncopyable types is similar to that in
- w0rp (18/18) Feb 10 2016 Back on the original topic, Scott Meyers often says "std::move
-
Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?=
(21/30)
Feb 10 2016
Well. In C++ "std::move(x)" is just a "static_cast
(x)" for - Atila Neves (19/50) Feb 11 2016 D has move semantics. Deep copies are done with post-blit. Fair
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (8/18) Feb 11 2016 In modern generics-oriented C++ I would say:
- Atila Neves (14/36) Feb 11 2016 Err... ok.
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (9/32) Feb 11 2016 I don't see how D's parameter semantics can be called move
- Matt Elkins (17/24) Feb 10 2016 Maybe this is what you are referring to, but the primary use I
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (17/24) Feb 10 2016 C++ unique_ptr is a semantically a reference-counting ptr with a
- Matt Elkins (13/26) Feb 10 2016 True, but with unique_ptr the max count is enforced by the
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (19/29) Feb 11 2016 You "subvert" both unique_ptr and shared_ptr by taking the
- Atila Neves (3/15) Feb 11 2016 @disable this(this) should be enough, no?
- Lars T. Kyllingstad (12/21) Feb 13 2016 This is my primary use case for move semantics too, in both C++
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
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. ShacharSo 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
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. ShacharShould work with move() from std.algorithm: http://dlang.org/library/std/algorithm/mutation/move.html
Feb 01 2016
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
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
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:Could you share this? I would be very interested in how you have approached it.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 02 2016
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
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
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
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: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); AliThis 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
Am 03.02.2016 um 00:21 schrieb Ali Çehreli:On 02/02/2016 03:09 PM, Ola Fosheim Grøstad 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.On Tuesday, 2 February 2016 at 22:36:22 UTC, Ali Çehreli wrote: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); AliThis 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 03 2016
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
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: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.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
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
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:Yeah, a ref parameter is more or less the only similar option.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.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
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: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. Larsseems 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 13 2016
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
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: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.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.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.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.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?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 guess this is what I don't understand. How and when does that happen? Larsexplain 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?
Feb 13 2016
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?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.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?
Feb 13 2016
On Saturday, 13 February 2016 at 19:25:37 UTC, Ola Fosheim Grøstad 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.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?
Feb 13 2016
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
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: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=5777D "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
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
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: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.[...]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. [...][...]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.htmlHere, 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...A swap is three moves -- actual moves.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...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.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?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.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.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?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
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
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 = aDuh, "b=tmp"...
Feb 13 2016
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: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.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.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.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?I was, yes.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.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
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
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
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
On Wednesday, 3 February 2016 at 18:04:38 UTC, Andrei Alexandrescu wrote:On 02/03/2016 10:05 AM, Sönke Ludwig 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++).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
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
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
On 02/03/2016 07:48 PM, Matt Elkins wrote:This [apparent] lack of clean move semanticsI very much wish there was a quick summary. I figure you've seen std.algorithm.move. What's missing? -- Andrei
Feb 03 2016
On Thursday, 4 February 2016 at 01:26:55 UTC, Andrei Alexandrescu wrote:On 02/03/2016 07:48 PM, Matt Elkins wrote: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 :).This [apparent] lack of clean move semanticsI very much wish there was a quick summary. I figure you've seen std.algorithm.move. What's missing? -- Andrei
Feb 03 2016
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
On Thursday, 4 February 2016 at 02:33:06 UTC, Andrei Alexandrescu wrote:On 02/03/2016 09:01 PM, Matt Elkins wrote:possibly related https://issues.dlang.org/show_bug.cgi?id=7579[...]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
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! -- AndreiI 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
On Thursday, 4 February 2016 at 02:33:06 UTC, Andrei Alexandrescu wrote:On 02/03/2016 09:01 PM, Matt Elkins wrote: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[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 04 2016
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! -- AndreiDone (sorry for the delay): https://issues.dlang.org/show_bug.cgi?id=15662
Feb 08 2016
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: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 HaraGot 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! -- AndreiDone (sorry for the delay): https://issues.dlang.org/show_bug.cgi?id=15662
Feb 09 2016
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:I see. Unfortunate. But thanks for the explanation!On Thursday, 4 February 2016 at 02:33:06 UTC, Andrei Alexandrescu wrote: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 HaraGot 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! -- AndreiDone (sorry for the delay): https://issues.dlang.org/show_bug.cgi?id=15662
Feb 09 2016
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: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();On Thursday, 4 February 2016 at 02:33:06 UTC, Andrei Alexandrescu wrote: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 HaraGot 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! -- AndreiDone (sorry for the delay): https://issues.dlang.org/show_bug.cgi?id=15662
Feb 10 2016
On 2/8/16 7:25 PM, Matt Elkins wrote:On Thursday, 4 February 2016 at 02:33:06 UTC, Andrei Alexandrescu wrote:Thx! -- AndreiGot 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! -- AndreiDone (sorry for the delay): https://issues.dlang.org/show_bug.cgi?id=15662
Feb 09 2016
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=15662I'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
On Thursday, 4 February 2016 at 01:26:55 UTC, Andrei Alexandrescu wrote:On 02/03/2016 07:48 PM, Matt Elkins 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.This [apparent] lack of clean move semanticsI very much wish there was a quick summary. I figure you've seen std.algorithm.move. What's missing? -- Andrei
Feb 03 2016
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:Those are intended not to be copied, they must be explicitly moved with std.algorithm's move[...]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
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: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?On Thursday, 4 February 2016 at 01:26:55 UTC, Andrei Alexandrescu wrote:Those are intended not to be copied, they must be explicitly moved with std.algorithm's move[...]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
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: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 : )On Thursday, 4 February 2016 at 03:45:57 UTC, maik klein wrote: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?[...]Those are intended not to be copied, they must be explicitly moved with std.algorithm's move
Feb 03 2016
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
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
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 stuffMaybe 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
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: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. AtilaBack 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 stuffMaybe 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 11 2016
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 rvaluesIn 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
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:Err... ok.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*It depends. For small structs, it is. And in some cases the compiler can elide the copy.C++: void foo(Foo); //copy void foo(Foo&); //by-ref, only lvalues void foo(Foo&&); //move, only rvaluesIn modern generics-oriented C++ I would say: void foo(T); // by value - and probably not what you wantvoid foo(const T&) // copy semantics overload void foo(T&&) // move semantics overloadI 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
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: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?On Thursday, 11 February 2016 at 14:25:39 UTC, Atila Neves wrote:Err... ok.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*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?void foo(const T&) // copy semantics overload void foo(T&&) // move semantics overloadI forgot the const. It doesn't change my point.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
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
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
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: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.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.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
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
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:disable this(this) should be enough, no? Atila[...]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. [...]
Feb 11 2016
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