Variables with scoped destruction in closures

ag0aep6g via Digitalmars-d-learn digitalmars-d-learn at puremagic.com
Fri Oct 14 07:00:53 PDT 2016


On 10/14/2016 12:18 PM, Nordlöw wrote:
> import std.algorithm.iteration : filter;
> import std.algorithm.mutation : move;
> import std.range : iota;
>
> static private struct S
> {
>     import core.memory : GC;
>     @disable this(this);
>
>     this(int x)
>     {
>         _ptr = cast(typeof(_ptr))GC.malloc((*_ptr).sizeof);
>         *_ptr = x;
>     }
>
>     ~this() { GC.free(_ptr); }  // scoped destruction
>
>     @property auto ref value() @safe pure nothrow @nogc { return *_ptr; }
>     alias value this;
>
>     int* _ptr;
> }
>
> auto below1(size_t n,
>             S s = S.init)
> {
>     // this could work, if the compiler could infer that
>     // `s` can be implicitly converted to an r-value
>     return 0.iota(n).filter!(_ => _ < s);
> }
>
> auto below2(size_t n,
>             S s = S.init)
> {
>     // this should work, because `s` is an r-value
>     return 0.iota(n).filter!(_ => _ < s.move());
> }
>
> unittest
> {
>     S s = S(42);
>     s.value = 43;
>     s.value = 42;
>     assert(s.value == 42);
>
>     // both these fail
>     100.below1(s.move());
>     100.below2(s.move());
> }
>
>
>
> fails to compile with DMD Git master with error message
>
> t_scope.d(23,6): Error: variable t_scope.below.s has scoped destruction,
> cannot build closure
>
> It illustrates one the simplest cases where a container-type cannot be
> used inside a lambda closure needed by D's great lazy ranges.
>
> Can somebody explain why not even `below2` compiles eventhough
> `s.move()` is inferred to be an r-value?

Your `s.move()` isn't called once when the closure is created, but every 
time the lambda is called. The closure must already be set up at that 
point. So s vs. s.move() doesn't make a difference with regards to 
closure creation.

> Until this gets fixed in the compiler, is there something I can do in
> the mean-while to make it possible to use instances of `S` inside of
> range lambda closures?

I don't see an obvious compiler bug. I'm not sure why exactly the 
compiler doesn't just move s to the closure, but it would at least be a 
bit surprising. The function would assume ownership of s without that 
being spelled out. There may be more serious issues which I'm not aware of.

As for ways to make this work:

1) You can move s to the heap yourself:

----
auto below3(size_t n, S s = S.init)
{
     import std.algorithm.mutation: moveEmplace;
     auto onHeap = cast(S*) new ubyte[S.sizeof];
     moveEmplace(s, *onHeap);
         /* If there's a function that does allocation and moveEmplace
         in one go, I can't find it. */
     return 0.iota(n).filter!(_ => _ < *onHeap);
}
----

2) Or you can move it into a struct that gets returned (more involved):

----
auto below4(size_t n, S s = S.init)
{
     static struct Below4CustomFilter(R)
     {
         R range;
         S s;

         this(R range, S s)
         {
             this.range = range;
             this.s = s.move();
             skipFiltered();
         }

         private void skipFiltered()
         {
             while (!range.empty && range.front >= s.value)
                 range.popFront();
         }

         @property bool empty() { return range.empty; }
         @property auto front() { return range.front; }
         void popFront() { range.popFront(); skipFiltered(); }
         /* ... more advanced range primitives ... */
     }
     static customFilter(R)(R range, S s)
     {
         return Below4CustomFilter!R(range, s.move());
     }
     return customFilter(0.iota(n), s.move());
}
----


More information about the Digitalmars-d-learn mailing list