Lambda surprise
Basile B.
b2.temp at gmx.com
Sat Feb 8 13:21:59 UTC 2020
On Saturday, 8 February 2020 at 06:21:50 UTC, Jean-Louis Leroy
wrote:
> While implementing support for parameter storage classes in my
> openmethods library, I ran into a puzzling error.
>
> While massaging code I came up with something like this:
>
> // dmd -c surprise.d
> import std.algorithm;
> import std.range;
>
> struct Method {
> static string foo() {
> enum foo = [ "a", "b" ].map!(x => x ~ x).join(":");
> return foo;
> }
> }
>
> pragma(msg, Method.foo()); // aa:bb
>
> Now this is clearly a compile time constant, so I thought I
> would make it simpler and more explicit:
>
> struct Method {
> enum foo = [ "a", "b" ].map!(x => x ~ x).join(":");
> }
>
> ...which got me this error:
> /home/jll/dlang/dmd-2.085.0/linux/bin64/../../src/phobos/std/algorithm/iteration.d(475): Error: `this.__lambda2` has no value
>
> It took me a while to realize that the root of the problem was
> that the lambda was trying to capture `this`. While fiddling I
> threw in a `static` in front of `enum`:
>
> struct Method {
> static enum foo = [ "a", "b" ].map!(x => x ~ x).join(":");
> }
>
> ...and I got a more useful error message:
> /home/jll/dlang/dmd-2.085.0/linux/bin64/../../src/phobos/std/algorithm/iteration.d(470): Error: function `surprise.Method.map!((x) => x ~ x).map!(string[]).map` need `this` to access member `map`
>
> Thus the first version works because the lambda is formed
> inside a static method. This works as well:
>
> struct Method {
> static string stutter(string s) { return s ~ s; }
> enum foo = [ "a", "b" ].map!(stutter).join(":");
> }
>
>
> I wonder:
> [...]
> 2/ Is the lambda capturing `this` inside a class, even if it is
> not referenced, a documented behavior? Is it the right thing to
> do?
The problem is a bit more subtle. It is that `map` becomes a
member of `Method`. This is a problem that i've seen several
times in bugzilla but more often people encounter it because
their predicate is not `static` (even at the global scope !).
This case could be solved by making the template map `static`.
A general fix would be to infer automatically the `static` STC on
templates but this is hard to implement (as I tried). Inference
would be required because if you add `static` everywhere in the
standard library you break all the uses that really require
`this`.
---
import std.algorithm.iteration, std.range, std.traits,
std.functional;
/*>>>*/ static /*<<<*/ template map(fun...)
if (fun.length >= 1)
{
auto map(Range)(Range r) if (isInputRange!(Unqual!Range))
{
import std.meta : AliasSeq, staticMap;
alias RE = ElementType!(Range);
alias _fun = unaryFun!fun;
alias _funs = AliasSeq!(_fun);
return MapResult!(_fun, Range)(r);
}
}
private static struct MapResult(alias fun, Range)
{
alias R = Unqual!Range;
R _input;
@property auto ref back()()
{
return fun(_input.back);
}
void popBack()()
{
_input.popBack();
}
this(R input)
{
_input = input;
}
@property bool empty()
{
return _input.empty;
}
void popFront()
{
assert(!empty, "Attempting to popFront an empty map.");
_input.popFront();
}
@property auto ref front()
{
assert(!empty, "Attempting to fetch the front of an empty
map.");
return fun(_input.front);
}
static if (isRandomAccessRange!R)
{
static if (is(typeof(_input[ulong.max])))
private alias opIndex_t = ulong;
else
private alias opIndex_t = uint;
auto ref opIndex(opIndex_t index)
{
return fun(_input[index]);
}
}
static if (hasLength!R)
{
@property auto length()
{
return _input.length;
}
}
static if (hasSlicing!R)
{
static if (is(typeof(_input[ulong.max .. ulong.max])))
private alias opSlice_t = ulong;
else
private alias opSlice_t = uint;
static if (hasLength!R)
{
auto opSlice(opSlice_t low, opSlice_t high)
{
return typeof(this)(_input[low .. high]);
}
}
}
}
struct Method {
enum foo = [ "a", "b" ].map!(x => x ~ x).join(":");
}
---
More information about the Digitalmars-d
mailing list