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());
}
----