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