Thread overview
Any workaround for "closures are not yet supported in CTFE"?
Dec 07, 2021
Andrey Zherikov
Dec 07, 2021
Ali Çehreli
Dec 08, 2021
Andrey Zherikov
Dec 08, 2021
Timon Gehr
Dec 08, 2021
Stanislav Blinov
Dec 08, 2021
Timon Gehr
Dec 09, 2021
Andrey Zherikov
December 07, 2021

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:

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?

December 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

December 08, 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?
December 08, 2021
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();
}

```
December 08, 2021

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.

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

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();
}
December 08, 2021

On Wednesday, 8 December 2021 at 08:07:59 UTC, Petar Kirov [ZombineDev] wrote:

>
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?

December 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:
>> 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/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();
> }
> ```

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

```
December 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:

>
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.
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.

December 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:

>

[...]

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:

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.

December 09, 2021

On Wednesday, 8 December 2021 at 17:05:49 UTC, Timon Gehr wrote:

>
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!