www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Any workaround for "closures are not yet supported in CTFE"?

reply Andrey Zherikov <andrey.zherikov gmail.com> writes:
I have a struct `A` that stores delegates. The problem is that 
I'm getting "`Error: closures are not yet supported in CTFE`" if 
I create an compile-time constant value of type `A`:

```d
struct A
{
     void delegate()[] dg;
}

auto createDelegate(string s)
{
     return { s.writeln; };       // Error: closures are not yet 
supported in CTFE
}

A create()
{
     A a;
     a.dg ~= createDelegate("hello");
     a.dg ~= createDelegate("buy");
     return a;
}


void main()
{
     enum a = create();         // If change 'enum' to 'auto' then 
everything works
     foreach(dg; a.dg)
         dg();
}
```

How can I workaround this and have compile-time object with 
delegates?
Dec 07 2021
parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 12/7/21 7:30 AM, Andrey Zherikov wrote:

 auto createDelegate(string s)
 {
      return { s.writeln; };       // Error: closures are not yet
 supported in CTFE
 }
I don't know whether the workaround works with your program but that delegate is the equivalent of the following struct (the struct should be faster because there is no dynamic context allocation). Note the type of 'dg' is changed accordingly: struct FunctionObject { string s; this(string s) { this.s = s; } auto opCall() { import std.stdio : writeln; s.writeln; } } struct A { FunctionObject[] dg; } auto createDelegate(string s) { return FunctionObject(s); } Ali
Dec 07 2021
parent reply Andrey Zherikov <andrey.zherikov gmail.com> writes:
On Tuesday, 7 December 2021 at 18:50:04 UTC, Ali Çehreli wrote:
 I don't know whether the workaround works with your program but 
 that delegate is the equivalent of the following struct (the 
 struct should be faster because there is no dynamic context 
 allocation). Note the type of 'dg' is changed accordingly:
The problem with struct-based solution is that I will likely be stuck with only one implementation of delegate (i.e. opCall implementation). Or I'll have to implement dispatching inside opCall based on some "enum" by myself which seems weird to me. Do I miss anything?
Dec 07 2021
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 08.12.21 03:05, Andrey Zherikov wrote:
 On Tuesday, 7 December 2021 at 18:50:04 UTC, Ali Çehreli wrote:
 I don't know whether the workaround works with your program but that 
 delegate is the equivalent of the following struct (the struct should 
 be faster because there is no dynamic context allocation). Note the 
 type of 'dg' is changed accordingly:
The problem with struct-based solution is that I will likely be stuck with only one implementation of delegate (i.e. opCall implementation). Or I'll have to implement dispatching inside opCall based on some "enum" by myself which seems weird to me. Do I miss anything?
This seems to work, maybe it is closer to what you are looking for. ```d import std.stdio, std.traits, core.lifetime; struct CtDelegate(R,T...){ void* ctx; R function(T,void*) fp; R delegate(T) get(){ R delegate(T) dg; dg.ptr=ctx; dg.funcptr=cast(typeof(dg.funcptr))fp; return dg; } alias get this; this(void* ctx,R function(T,void*) fp){ this.ctx=ctx; this.fp=fp; } R opCall(T args){ return fp(args,ctx); } } auto makeCtDelegate(alias f,C)(C ctx){ static struct Ctx{ C ctx; } return CtDelegate!(ReturnType!(typeof(f)),ParameterTypeTuple!f[0..$-1])(new Ctx(forward!ctx), (ParameterTypeTuple!f[0..$-1] args,void* ctx){ auto r=cast(Ctx*)ctx; return f(r.ctx,forward!args); }); } struct A{ CtDelegate!void[] dg; } auto createDelegate(string s){ return makeCtDelegate!((string s){ s.writeln; })(s); } A create(){ A a; a.dg ~= createDelegate("hello"); a.dg ~= createDelegate("buy"); return a; } void main(){ static a = create(); foreach(dg; a.dg) dg(); } ```
Dec 07 2021
parent reply Petar Kirov [ZombineDev] <petar.p.kirov gmail.com> writes:
On Wednesday, 8 December 2021 at 07:55:55 UTC, Timon Gehr wrote:
 On 08.12.21 03:05, Andrey Zherikov wrote:
 On Tuesday, 7 December 2021 at 18:50:04 UTC, Ali Çehreli wrote:
 I don't know whether the workaround works with your program 
 but that delegate is the equivalent of the following struct 
 (the struct should be faster because there is no dynamic 
 context allocation). Note the type of 'dg' is changed 
 accordingly:
The problem with struct-based solution is that I will likely be stuck with only one implementation of delegate (i.e. opCall implementation). Or I'll have to implement dispatching inside opCall based on some "enum" by myself which seems weird to me. Do I miss anything?
This seems to work, maybe it is closer to what you are looking for. ```d import std.stdio, std.traits, core.lifetime; struct CtDelegate(R,T...){ void* ctx; R function(T,void*) fp; R delegate(T) get(){ R delegate(T) dg; dg.ptr=ctx; dg.funcptr=cast(typeof(dg.funcptr))fp; return dg; } alias get this; this(void* ctx,R function(T,void*) fp){ this.ctx=ctx; this.fp=fp; } R opCall(T args){ return fp(args,ctx); } } auto makeCtDelegate(alias f,C)(C ctx){ static struct Ctx{ C ctx; } return CtDelegate!(ReturnType!(typeof(f)),ParameterTypeTuple!f[0..$-1])(new Ctx(forward!ctx), (ParameterTypeTuple!f[0..$-1] args,void* ctx){ auto r=cast(Ctx*)ctx; return f(r.ctx,forward!args); }); } struct A{ CtDelegate!void[] dg; } auto createDelegate(string s){ return makeCtDelegate!((string s){ s.writeln; })(s); } A create(){ A a; a.dg ~= createDelegate("hello"); a.dg ~= createDelegate("buy"); return a; } void main(){ static a = create(); foreach(dg; a.dg) dg(); } ```
Incidentally, yesterday I played with a very similar solution. Here's my version: https://run.dlang.io/gist/PetarKirov/f347e59552dd87c4c02d0ce87d0e9cdc?compiler=dmd ```d interface ICallable { void opCall() const; } auto makeDelegate(alias fun, Args...)(auto ref Args args) { return new class(args) ICallable { Args m_args; this(Args p_args) { m_args = p_args; } void opCall() const { fun(m_args); } }; } alias Action = void delegate(); Action createDelegate(string s) { import std.stdio; return &makeDelegate!((string str) => writeln(str))(s).opCall; } struct A { Action[] dg; } A create() { A a; a.dg ~= createDelegate("hello"); a.dg ~= createDelegate("buy"); return a; } void main() { enum a = create(); foreach(dg; a.dg) dg(); } ```
Dec 08 2021
next sibling parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Wednesday, 8 December 2021 at 08:07:59 UTC, Petar Kirov 
[ZombineDev] wrote:

 ```d
 interface ICallable
 {
     void opCall() const;
 }

 alias Action = void delegate();

 struct A
 {
     Action[] dg;
 }
 ```
At this point why not just call a spade a spade and store an array of ICallables directly? :) I mean, why store fat pointers to fat pointers?
Dec 08 2021
parent Petar Kirov [ZombineDev] <petar.p.kirov gmail.com> writes:
On Wednesday, 8 December 2021 at 12:17:42 UTC, Stanislav Blinov 
wrote:
 On Wednesday, 8 December 2021 at 08:07:59 UTC, Petar Kirov 
 [ZombineDev] wrote:

 ```d
 interface ICallable
 {
     void opCall() const;
 }

 alias Action = void delegate();

 struct A
 {
     Action[] dg;
 }
 ```
At this point why not just call a spade a spade and store an array of ICallables directly? :) I mean, why store fat pointers to fat pointers?
Initially that's exactly what I tried, and it worked if the result was stored as `static const` / `static immutable`, but it didn't when using the `enum`: ``` onlineapp.d(39): Error: variable `onlineapp.main.a` : Unable to initialize enum with class or pointer to struct. Use static const variable instead. ``` ```d interface ICallable { void opCall() const; } auto makeDelegate(alias fun, Args...)(auto ref Args args) { return new class(args) ICallable { Args m_args; this(Args p_args) { m_args = p_args; } void opCall() const { fun(m_args); } }; } alias Action = void delegate(); ICallable createDelegate(string s) { import std.stdio; return makeDelegate!((string str) => writeln(str))(s); } struct A { ICallable[] dg; } A create() { A a; a.dg ~= createDelegate("hello"); a.dg ~= createDelegate("buy"); return a; } void main() { enum a = create(); foreach(dg; a.dg) dg(); } ``` I didn't have time to fully investigate the issue and report this compiler limitation.
Dec 08 2021
prev sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 12/8/21 9:07 AM, Petar Kirov [ZombineDev] wrote:
 On Wednesday, 8 December 2021 at 07:55:55 UTC, Timon Gehr wrote:
 On 08.12.21 03:05, Andrey Zherikov wrote:
 On Tuesday, 7 December 2021 at 18:50:04 UTC, Ali Çehreli wrote:
 I don't know whether the workaround works with your program but that 
 delegate is the equivalent of the following struct (the struct 
 should be faster because there is no dynamic context allocation). 
 Note the type of 'dg' is changed accordingly:
The problem with struct-based solution is that I will likely be stuck with only one implementation of delegate (i.e. opCall implementation). Or I'll have to implement dispatching inside opCall based on some "enum" by myself which seems weird to me. Do I miss anything?
This seems to work, maybe it is closer to what you are looking for. ...
Incidentally, yesterday I played with a very similar solution. Here's my version: https://run.dlang.io/gist/PetarKirov/f347e59552dd87c4c02d0ce87 0e9cdc?compiler=dmd ```d interface ICallable {     void opCall() const; } auto makeDelegate(alias fun, Args...)(auto ref Args args) {     return new class(args) ICallable     {         Args m_args;         this(Args p_args) { m_args = p_args; }         void opCall() const { fun(m_args); }     }; } alias Action = void delegate(); Action createDelegate(string s) {     import std.stdio;     return &makeDelegate!((string str) => writeln(str))(s).opCall; } struct A {     Action[] dg; } A create() {     A a;     a.dg ~= createDelegate("hello");     a.dg ~= createDelegate("buy");     return a; } void main() {     enum a = create();     foreach(dg; a.dg)         dg(); } ```
Nice, so the error message is lying. This is a bit more complete: ```d import std.stdio, std.traits, core.lifetime; auto partiallyApply(alias fun,C...)(C context){ return &new class(move(context)){ C context; this(C context) { foreach(i,ref c;this.context) c=move(context[i]); } auto opCall(ParameterTypeTuple!fun[context.length..$] args) { return fun(context,forward!args); } }.opCall; } alias Action = void delegate(); Action createDelegate(string s){ import std.stdio; return partiallyApply!((string str) => writeln(str))(s); } struct A{ Action[] dg; } A create(){ A a; a.dg ~= createDelegate("hello"); a.dg ~= createDelegate("buy"); return a; } void main(){ enum a = create(); foreach(dg; a.dg) dg(); } ```
Dec 08 2021
next sibling parent Petar Kirov [ZombineDev] <petar.p.kirov gmail.com> writes:
On Wednesday, 8 December 2021 at 17:05:49 UTC, Timon Gehr wrote:
 On 12/8/21 9:07 AM, Petar Kirov [ZombineDev] wrote:
 [...]
Nice, so the error message is lying.
Closure support deserves way more love in the compiler. I'm quite surprised that that hack worked, given that various very similar rearrangements that I tried before didn't.
 This is a bit more complete:

 ```d
 import std.stdio, std.traits, core.lifetime;
 auto partiallyApply(alias fun,C...)(C context){
     return &new class(move(context)){
         C context;
         this(C context) { foreach(i,ref c;this.context) 
 c=move(context[i]); }
         auto opCall(ParameterTypeTuple!fun[context.length..$] 
 args) {
             return fun(context,forward!args);
         }
     }.opCall;
 }

 // [snip]

 ```
Thanks, I was struggling to find a good name for this building block. `partiallyApply` is a natural fit. Also thanks for the move / forwarding icing.
Dec 08 2021
prev sibling parent Andrey Zherikov <andrey.zherikov gmail.com> writes:
On Wednesday, 8 December 2021 at 17:05:49 UTC, Timon Gehr wrote:
 ```d
 import std.stdio, std.traits, core.lifetime;
 auto partiallyApply(alias fun,C...)(C context){
     return &new class(move(context)){
         C context;
         this(C context) { foreach(i,ref c;this.context) 
 c=move(context[i]); }
         auto opCall(ParameterTypeTuple!fun[context.length..$] 
 args) {
             return fun(context,forward!args);
         }
     }.opCall;
 }

 alias Action = void delegate();

 Action createDelegate(string s){
     import std.stdio;
     return partiallyApply!((string str) => writeln(str))(s);
 }

 struct A{ Action[] dg; }

 A create(){
     A a;
     a.dg ~= createDelegate("hello");
     a.dg ~= createDelegate("buy");
     return a;
 }

 void main(){
     enum a = create();
     foreach(dg; a.dg)
         dg();
 }

 ```
This is great, thanks you!
Dec 08 2021