Jump to page: 1 2
Thread overview
Better handling of noncopyable objects and objects with this(this)
Jun 01, 2015
Jonathan M Davis
Jun 01, 2015
Namespace
Jun 01, 2015
Jonathan M Davis
Jun 02, 2015
ketmar
Jun 01, 2015
Marc Schütz
Jun 01, 2015
Jonathan M Davis
Jun 01, 2015
Marc Schütz
Jun 01, 2015
Jonathan M Davis
Jun 01, 2015
Marc Schütz
Jun 01, 2015
Marc Schütz
Jun 01, 2015
Marc Schütz
Jun 01, 2015
Jonathan M Davis
Jun 01, 2015
Timon Gehr
Jun 01, 2015
IgorStepanov
June 01, 2015
FYI I just created https://issues.dlang.org/show_bug.cgi?id=14638 as one of possibly several language enhancements to improve usability of noncopyable types (most allocators are not copyable) and to enhance performance of objects that define this(this). -- Andrei
June 01, 2015
On Monday, 1 June 2015 at 04:43:20 UTC, Andrei Alexandrescu wrote:
> FYI I just created https://issues.dlang.org/show_bug.cgi?id=14638 as one of possibly several language enhancements to improve usability of noncopyable types (most allocators are not copyable) and to enhance performance of objects that define this(this). -- Andrei

On a related note, as was brought up recently (by Ketmar IIRC), ranges currently forbid noncopyable objects thanks to

    auto h = r.front; // can get the front of the range

in isInputRange. If we want to fix that, then we're probably going to need to change isInputRange so that it checks that we can access front but doesn't require copying and then add something like hasCopyableElements for the algorithms that do need to copy front. I'm not a huge fan of that idea given how much code exists which copies front and how that would likely require that a lot of range-based functions add even more checks to their template constraints, but I'm not sure what else we can reasonably do if we want noncopyable elements to work in ranges, and the change wouldn't break existing code, just make it so that much of it would need updated template constraints in order to avoid compilation errors if anyone ever tries to use a range with noncopyable elements with it.

- Jonathan M Davis
June 01, 2015
On Monday, 1 June 2015 at 10:18:35 UTC, Jonathan M Davis wrote:
> On Monday, 1 June 2015 at 04:43:20 UTC, Andrei Alexandrescu wrote:
>> FYI I just created https://issues.dlang.org/show_bug.cgi?id=14638 as one of possibly several language enhancements to improve usability of noncopyable types (most allocators are not copyable) and to enhance performance of objects that define this(this). -- Andrei
>
> On a related note, as was brought up recently (by Ketmar IIRC), ranges currently forbid noncopyable objects thanks to
>
>     auto h = r.front; // can get the front of the range
>
> in isInputRange. If we want to fix that, then we're probably going to need to change isInputRange so that it checks that we can access front but doesn't require copying and then add something like hasCopyableElements for the algorithms that do need to copy front. I'm not a huge fan of that idea given how much code exists which copies front and how that would likely require that a lot of range-based functions add even more checks to their template constraints, but I'm not sure what else we can reasonably do if we want noncopyable elements to work in ranges, and the change wouldn't break existing code, just make it so that much of it would need updated template constraints in order to avoid compilation errors if anyone ever tries to use a range with noncopyable elements with it.
>
> - Jonathan M Davis

What about
----
auto h = &r.front; // can get the front of the range
----
?
June 01, 2015
On Monday, 1 June 2015 at 04:43:20 UTC, Andrei Alexandrescu wrote:
> FYI I just created https://issues.dlang.org/show_bug.cgi?id=14638 as one of possibly several language enhancements to improve usability of noncopyable types (most allocators are not copyable) and to enhance performance of objects that define this(this). -- Andrei

What do "static use" and "dynamic use" mean here?

Also, object with destructors need to have more restrictions:

    S {
        ~this();
    }

    void foo() {
        S s;
        if(condition)
            bar(s);
        // <- should we run the destructor here?
    }

This can either be solved by making such cases non-eligible, or by "remembering" whether an object was moved using a hidden boolean variable. AFAIK the latter is incidentally the solution Rust chose.
June 01, 2015
On Monday, 1 June 2015 at 11:03:30 UTC, Namespace wrote:
> What about
> ----
> auto h = &r.front; // can get the front of the range
> ----
> ?

hasLvalueElements checks whether you can pass the return value of r.front by ref and take its address. So, if you want to take the address of r.front, you should be using hasLvalueElements in your template constraint to prevent ranges which won't allow you to take the address of their front from being use with that function and resulting in compilation errors inside of the function.

- Jonathan M Davis
June 01, 2015
On Monday, 1 June 2015 at 12:50:28 UTC, Marc Schütz wrote:
> Also, object with destructors need to have more restrictions:
>
>     S {
>         ~this();
>     }
>
>     void foo() {
>         S s;
>         if(condition)
>             bar(s);
>         // <- should we run the destructor here?
>     }
>
> This can either be solved by making such cases non-eligible, or by "remembering" whether an object was moved using a hidden boolean variable. AFAIK the latter is incidentally the solution Rust chose.

Wouldn't the compiler just do something like

if(condition)
{
    bar(s); // Do a move; the destructor is run inside bar
    return;
}
else
{
    s.__dtor();
    return;
}

In which case, the destructor is always run in the right spot, and the compiler can guarantee that a move is done rather than a copy?

- Jonathan M Davis
June 01, 2015
On Monday, 1 June 2015 at 14:20:46 UTC, Jonathan M Davis wrote:
> On Monday, 1 June 2015 at 12:50:28 UTC, Marc Schütz wrote:
>> Also, object with destructors need to have more restrictions:
>>
>>    S {
>>        ~this();
>>    }
>>
>>    void foo() {
>>        S s;
>>        if(condition)
>>            bar(s);
>>        // <- should we run the destructor here?
>>    }
>>
>> This can either be solved by making such cases non-eligible, or by "remembering" whether an object was moved using a hidden boolean variable. AFAIK the latter is incidentally the solution Rust chose.
>
> Wouldn't the compiler just do something like
>
> if(condition)
> {
>     bar(s); // Do a move; the destructor is run inside bar
>     return;
> }
> else
> {
>     s.__dtor();
>     return;
> }
>
> In which case, the destructor is always run in the right spot, and the compiler can guarantee that a move is done rather than a copy?

When this can be done, it's of course a good solution. But when there is additional code between the move and the destruction, it usually doesn't work:

    void foo() {
        S s;
        if(condition)
            bar(s);
        baz();
        // <- should we run the destructor here?
    }
June 01, 2015
On Monday, 1 June 2015 at 14:43:22 UTC, Marc Schütz wrote:
> On Monday, 1 June 2015 at 14:20:46 UTC, Jonathan M Davis wrote:
>> On Monday, 1 June 2015 at 12:50:28 UTC, Marc Schütz wrote:
>>> Also, object with destructors need to have more restrictions:
>>>
>>>   S {
>>>       ~this();
>>>   }
>>>
>>>   void foo() {
>>>       S s;
>>>       if(condition)
>>>           bar(s);
>>>       // <- should we run the destructor here?
>>>   }
>>>
>>> This can either be solved by making such cases non-eligible, or by "remembering" whether an object was moved using a hidden boolean variable. AFAIK the latter is incidentally the solution Rust chose.
>>
>> Wouldn't the compiler just do something like
>>
>> if(condition)
>> {
>>    bar(s); // Do a move; the destructor is run inside bar
>>    return;
>> }
>> else
>> {
>>    s.__dtor();
>>    return;
>> }
>>
>> In which case, the destructor is always run in the right spot, and the compiler can guarantee that a move is done rather than a copy?
>
> When this can be done, it's of course a good solution. But when there is additional code between the move and the destruction, it usually doesn't work:
>
>     void foo() {
>         S s;
>         if(condition)
>             bar(s);
>         baz();
>         // <- should we run the destructor here?
>     }

Then, like you suggested, that would indeed appear to require either an invisible bool that indicates whether a move was done or not or disallowing the move. I think that I'd favor the bool, since moving is likely to save more than the extra bool is going to cost.

- Jonathan M Davis
June 01, 2015
On Monday, 1 June 2015 at 14:59:38 UTC, Jonathan M Davis wrote:
> On Monday, 1 June 2015 at 14:43:22 UTC, Marc Schütz wrote:
>> On Monday, 1 June 2015 at 14:20:46 UTC, Jonathan M Davis wrote:
>>> On Monday, 1 June 2015 at 12:50:28 UTC, Marc Schütz wrote:
>>>> Also, object with destructors need to have more restrictions:
>>>>
>>>>  S {
>>>>      ~this();
>>>>  }
>>>>
>>>>  void foo() {
>>>>      S s;
>>>>      if(condition)
>>>>          bar(s);
>>>>      // <- should we run the destructor here?
>>>>  }
>>>>
>>>> This can either be solved by making such cases non-eligible, or by "remembering" whether an object was moved using a hidden boolean variable. AFAIK the latter is incidentally the solution Rust chose.
>>>
>>> Wouldn't the compiler just do something like
>>>
>>> if(condition)
>>> {
>>>   bar(s); // Do a move; the destructor is run inside bar
>>>   return;
>>> }
>>> else
>>> {
>>>   s.__dtor();
>>>   return;
>>> }
>>>
>>> In which case, the destructor is always run in the right spot, and the compiler can guarantee that a move is done rather than a copy?
>>
>> When this can be done, it's of course a good solution. But when there is additional code between the move and the destruction, it usually doesn't work:
>>
>>    void foo() {
>>        S s;
>>        if(condition)
>>            bar(s);
>>        baz();
>>        // <- should we run the destructor here?
>>    }
>
> Then, like you suggested, that would indeed appear to require either an invisible bool that indicates whether a move was done or not or disallowing the move. I think that I'd favor the bool, since moving is likely to save more than the extra bool is going to cost.

Yes, and I think an optimizer can even eliminate the cost in some cases, e.g. do the rewrite you suggested.

For reference, here are some discussions the Rust community had about this topic (didn't read them completely):

https://github.com/rust-lang/rfcs/pull/320
https://github.com/rust-lang/rfcs/blob/master/text/0320-nonzeroing-dynamic-drop.md
https://github.com/pnkfelix/rfcs/blob/a773ba113ba135ddb7f481c4829882733eaa5355/active/0000-remove-drop-flag-and-zeroing.md#require-explicit-drops-rather-than-injecting-them

Some of the suggested solutions are not applicable for us, because we don't have to deal with explicit moves like they do. But we can certainly learn from their discussions.
June 01, 2015
There is another solution that doesn't require a hidden variable, at the cost of duplicating generated code:

    void foo() {
        S s;
        if(cond)
            bar(s);
        some();
        more();
        code();
    }

Can be rewritten as:

    void foo() {
        S s;
        if(cond) {
            bar(s);
            some();
            more();
            code();
            // don't destroy here
        } else {
            some();
            more();
            code();
            s.~this(); // destroy here
        }
    }

But I think this is best left to an optimizer which has more info about the costs of the additional code vs the hidden var to decide whether its worth it.
« First   ‹ Prev
1 2