digitalmars.D.learn - Any workaround for "closures are not yet supported in CTFE"?
- Andrey Zherikov (30/30) Dec 07 2021 I have a struct `A` that stores delegates. The problem is that
- =?UTF-8?Q?Ali_=c3=87ehreli?= (23/28) Dec 07 2021 I don't know whether the workaround works with your program but that
- Andrey Zherikov (6/10) Dec 07 2021 The problem with struct-based solution is that I will likely be
- Timon Gehr (43/53) Dec 07 2021 This seems to work, maybe it is closer to what you are looking for.
- Petar Kirov [ZombineDev] (42/98) Dec 08 2021 Incidentally, yesterday I played with a very similar solution.
- Stanislav Blinov (5/16) Dec 08 2021 At this point why not just call a spade a spade and store an
- Petar Kirov [ZombineDev] (50/68) Dec 08 2021 Initially that's exactly what I tried, and it worked if the
- Timon Gehr (32/101) Dec 08 2021 Nice, so the error message is lying. This is a bit more complete:
- Petar Kirov [ZombineDev] (7/26) Dec 08 2021 Closure support deserves way more love in the compiler. I'm quite
- Andrey Zherikov (2/33) Dec 08 2021 This is great, thanks you!
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
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
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
On 08.12.21 03:05, Andrey Zherikov wrote:On Tuesday, 7 December 2021 at 18:50:04 UTC, Ali Çehreli wrote: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(); } ```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
On Wednesday, 8 December 2021 at 07:55:55 UTC, Timon Gehr wrote:On 08.12.21 03:05, Andrey Zherikov wrote: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(); } ```On Tuesday, 7 December 2021 at 18:50:04 UTC, Ali Çehreli wrote: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(); } ```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 08 2021
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
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: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.```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
On 12/8/21 9:07 AM, Petar Kirov [ZombineDev] wrote:On Wednesday, 8 December 2021 at 07:55:55 UTC, Timon Gehr wrote: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(); } ```On 08.12.21 03:05, Andrey Zherikov wrote: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(); } ```On Tuesday, 7 December 2021 at 18:50:04 UTC, Ali Çehreli wrote:This seems to work, maybe it is closer to what you are looking for. ...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 08 2021
On Wednesday, 8 December 2021 at 17:05:49 UTC, Timon Gehr wrote:On 12/8/21 9:07 AM, Petar Kirov [ZombineDev] wrote: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.[...]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; } // [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
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