www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Better handling of noncopyable objects and objects with this(this)

reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
May 31 2015
next sibling parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
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
Jun 01 2015
parent reply "Namespace" <rswhite4 gmail.com> writes:
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 ---- ?
Jun 01 2015
next sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
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
Jun 01 2015
prev sibling parent ketmar <ketmar ketmar.no-ip.org> writes:
On Mon, 01 Jun 2015 11:03:29 +0000, Namespace wrote:

 What about ----
 auto h =3D &r.front; // can get the front of the range ----
 ?
there is funnier trick: static void testfront(T) (auto ref T n) {} testfront(r.front); the thing is that we don't really want to check if we can take an address=20 of front value, we want to keep `ref` if it's there. with this magic=20 template `ref` is not stripped, but we still don't require "&" to work.=
Jun 02 2015
prev sibling next sibling parent reply "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> writes:
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.
Jun 01 2015
next sibling parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
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
Jun 01 2015
parent reply "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> writes:
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? }
Jun 01 2015
parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
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
Jun 01 2015
parent reply "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> writes:
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.
Jun 01 2015
parent "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> writes:
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.
Jun 01 2015
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 6/1/15 5:50 AM, "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net>" 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
What do "static use" and "dynamic use" mean here?
static = as you read the code dynamic = as you run the code
 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.
This has been solved, we have move which obliterates the source with .init. Andrei
Jun 01 2015
parent reply "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> writes:
On Monday, 1 June 2015 at 15:45:11 UTC, Andrei Alexandrescu wrote:
 On 6/1/15 5:50 AM, "Marc =?UTF-8?B?U2Now7x0eiI=?= 
 <schuetzm gmx.net>" 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
What do "static use" and "dynamic use" mean here?
static = as you read the code dynamic = as you run the code
 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.
This has been solved, we have move which obliterates the source with .init.
Huh? What does that have to do with the current topic? The question is not what should happen when someone does a conditional _explicit_ move, but whether a move should be done _implicitly_ by the compiler given the above constellation, and how it deals with the destructor if yes.
Jun 01 2015
next sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Monday, 1 June 2015 at 16:40:38 UTC, Marc Schütz wrote:
 On Monday, 1 June 2015 at 15:45:11 UTC, Andrei Alexandrescu 
 wrote:
 On 6/1/15 5:50 AM, "Marc =?UTF-8?B?U2Now7x0eiI=?= 
 <schuetzm gmx.net>" wrote:
 On Monday, 1 June 2015 at 04:43:20 UTC, Andrei Alexandrescu 
 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.
This has been solved, we have move which obliterates the source with .init.
Huh? What does that have to do with the current topic? The question is not what should happen when someone does a conditional _explicit_ move, but whether a move should be done _implicitly_ by the compiler given the above constellation, and how it deals with the destructor if yes.
Obviously, only Andrei can say for sure what he meant, but I would guess that he was suggesting that in the case of if(condition) bar(s); it would set s to S.init when it's moved for the call to bar so that the destructor can be run when s exits scope regardless of which branch was executed - though that does mean that the destructor is run twice when bar(s) is called (once inside of Bar for the original value and once for the init value) - so while that solution is correct and straightforward, I'm not sure that it's the best one. - Jonathan M Davis
Jun 01 2015
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 6/1/15 9:40 AM, "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net>" wrote:
 The question is not what should happen when someone does a conditional
 _explicit_ move, but whether a move should be done _implicitly_ by the
 compiler given the above constellation, and how it deals with the
 destructor if yes.
At language level the compiler has control over generating the destructor, so it won't if it's doing a move. Note that this is STATIC analysis. -- Andrei
Jun 01 2015
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 06/01/2015 07:30 PM, Andrei Alexandrescu wrote:
 On 6/1/15 9:40 AM, "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net>"
 wrote:
 The question is not what should happen when someone does a conditional
 _explicit_ move, but whether a move should be done _implicitly_ by the
 compiler given the above constellation, and how it deals with the
 destructor if yes.
At language level the compiler has control over generating the destructor, so it won't if it's doing a move. Note that this is STATIC analysis. -- Andrei
Static analysis is perfectly capable of figuring this case out. He is asking about how much sophistication will be guaranteed by the language specification, and what the exact rules are.
Jun 01 2015
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 6/1/15 10:38 AM, Timon Gehr wrote:
 On 06/01/2015 07:30 PM, Andrei Alexandrescu wrote:
 On 6/1/15 9:40 AM, "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net>"
 wrote:
 The question is not what should happen when someone does a conditional
 _explicit_ move, but whether a move should be done _implicitly_ by the
 compiler given the above constellation, and how it deals with the
 destructor if yes.
At language level the compiler has control over generating the destructor, so it won't if it's doing a move. Note that this is STATIC analysis. -- Andrei
Static analysis is perfectly capable of figuring this case out. He is asking about how much sophistication will be guaranteed by the language specification, and what the exact rules are.
It should be enough to do the move only if all paths of execution end in the same conversion to rvalue. -- Andrei
Jun 01 2015
prev sibling next sibling parent "IgorStepanov" <wazar mail.ru> writes:
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
My last year's thoughts about that: https://issues.dlang.org/show_bug.cgi?id=13492
Jun 01 2015
prev sibling parent Joseph Rushton Wakeling via Digitalmars-d <digitalmars-d puremagic.com> writes:
On 01/06/15 06:43, Andrei Alexandrescu via Digitalmars-d 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
Having just been playing with noncopyable types at the suggestion of Dicebot, Steve Schweighoffer and Martin Nowak, thanks for drawing attention to this. I can see it being very useful to address.
Jun 01 2015