February 03, 2016
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.

February 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?



February 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:
>> 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.
February 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.

February 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:
>> 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.
February 03, 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
February 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:
>> 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++).
February 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
February 04, 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++!
February 03, 2016
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