www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Calling template member function?

reply Andrey Zherikov <andrey.zherikov gmail.com> writes:
I want to migrate my library API from standalone function that 
takes delegate as argument to a template member function that 
takes delegate as a template parameter but compiler errors out. 
Here is code example:
```d
import std.stdio;

template T(P)
{
     static void f_new(alias func)()
     {
         func();
     }
}

void f(FUNC)(FUNC func)
{
     T!int.f_new!(() => func());
}

void main()
{
     f(function () { __LINE__.writeln; });
}
```

Compiler error:
```
onlineapp.d(7): Error: `static` function `onlineapp.f!(void 
function()  safe).f.f_new!(delegate ()  safe
{
(*func)();
return ;
}
).f_new` cannot access delegate `__lambda2` in frame of function 
`onlineapp.f!(void function()  safe).f`
onlineapp.d(13):        `__lambda2` declared here
onlineapp.d(13): Error: template instance `onlineapp.f!(void 
function()  safe).f.f_new!(delegate ()  safe
{
(*func)();
return ;
}
)` error instantiating
onlineapp.d(18):        instantiated from here: `f!(void 
function()  safe)`
```

What confuses me a lot is that if I remove `template T` then 
everything works perfectly:
```d
import std.stdio;

void f_new(alias func)()
{
     func();
}

void f(FUNC)(FUNC func)
{
     f_new!(() => func());
}

void main()
{
     f(function () { __LINE__.writeln; });
}
```

Is there an issue in template processing in compiler?
Apr 19 2022
next sibling parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 4/19/22 9:36 AM, Andrey Zherikov wrote:
 I want to migrate my library API from standalone function that takes 
 delegate as argument to a template member function that takes delegate 
 as a template parameter but compiler errors out. Here is code example:
 ```d
 import std.stdio;
 
 template T(P)
 {
      static void f_new(alias func)()
      {
          func();
      }
 }
 
 void f(FUNC)(FUNC func)
 {
      T!int.f_new!(() => func());
 }
 
 void main()
 {
      f(function () { __LINE__.writeln; });
 }
 ```
 
 Compiler error:
 ```
 onlineapp.d(7): Error: `static` function `onlineapp.f!(void function() 
  safe).f.f_new!(delegate ()  safe
 {
 (*func)();
 return ;
 }
 ).f_new` cannot access delegate `__lambda2` in frame of function 
 `onlineapp.f!(void function()  safe).f`
 onlineapp.d(13):        `__lambda2` declared here
 onlineapp.d(13): Error: template instance `onlineapp.f!(void function() 
  safe).f.f_new!(delegate ()  safe
 {
 (*func)();
 return ;
 }
 )` error instantiating
 onlineapp.d(18):        instantiated from here: `f!(void function()
 safe)`
 ```
 
 What confuses me a lot is that if I remove `template T` then everything 
 works perfectly:
 ```d
 import std.stdio;
 
 void f_new(alias func)()
 {
      func();
 }
 
 void f(FUNC)(FUNC func)
 {
      f_new!(() => func());
 }
 
 void main()
 {
      f(function () { __LINE__.writeln; });
 }
 ```
 
 Is there an issue in template processing in compiler?
Something similar just recently came up on discord. Maybe related: https://issues.dlang.org/show_bug.cgi?id=17063 -Steve
Apr 19 2022
prev sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Tuesday, 19 April 2022 at 13:36:26 UTC, Andrey Zherikov wrote:
 I want to migrate my library API from standalone function that 
 takes delegate as argument to a template member function that 
 takes delegate as a template parameter but compiler errors out. 
 Here is code example:
 ```d
 import std.stdio;

 template T(P)
 {
     static void f_new(alias func)()
     {
         func();
     }
 }

 void f(FUNC)(FUNC func)
 {
     T!int.f_new!(() => func());
 }

 void main()
 {
     f(function () { __LINE__.writeln; });
 }
 ```

 Compiler error:
 ```
 onlineapp.d(7): Error: `static` function `onlineapp.f!(void 
 function()  safe).f.f_new!(delegate ()  safe
 {
 (*func)();
 return ;
 }
 ).f_new` cannot access delegate `__lambda2` in frame of 
 function `onlineapp.f!(void function()  safe).f`
 onlineapp.d(13):        `__lambda2` declared here
 onlineapp.d(13): Error: template instance `onlineapp.f!(void 
 function()  safe).f.f_new!(delegate ()  safe
 {
 (*func)();
 return ;
 }
 )` error instantiating
 onlineapp.d(18):        instantiated from here: `f!(void 
 function()  safe)`
 ```

 What confuses me a lot is that if I remove `template T` then 
 everything works perfectly:
The message is different, but I think this error is probably related to the "dual context" issue: https://issues.dlang.org/show_bug.cgi?id=5710 Basically, `f_new!(() => func())` has two scopes that it "wants" to be nested in: `T!int` and `f!(void function() safe)`. When you remove `template T`, there's only one scope, so it's not a problem. If you remove `static` from `f_new`, you get an error message talking about this explicitly: ``` onlineapp.d(5): Deprecation: function `onlineapp.f!(void function() safe).f.f_new!(delegate () safe { (*func)(); return ; } ).f_new` function requires a dual-context, which is deprecated onlineapp.d(13): instantiated from here: `f_new!(delegate () safe { (*func)(); return ; } )` onlineapp.d(18): instantiated from here: `f!(void function() safe)` onlineapp.d(13): Error: function `onlineapp.f!(void function() safe).f.f_new!(delegate () safe { (*func)(); return ; } ).f_new` is a nested function and cannot be accessed from `onlineapp.f!(void function() safe).f` ```
Apr 19 2022
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 4/19/22 11:46 AM, Paul Backus wrote:
 On Tuesday, 19 April 2022 at 13:36:26 UTC, Andrey Zherikov wrote:
 I want to migrate my library API from standalone function that takes 
 delegate as argument to a template member function that takes delegate 
 as a template parameter but compiler errors out. Here is code example:
 ```d
 import std.stdio;

 template T(P)
 {
     static void f_new(alias func)()
     {
         func();
     }
 }

 void f(FUNC)(FUNC func)
 {
     T!int.f_new!(() => func());
 }

 void main()
 {
     f(function () { __LINE__.writeln; });
 }
 ```

 Compiler error:
 ```
 onlineapp.d(7): Error: `static` function `onlineapp.f!(void function() 
  safe).f.f_new!(delegate ()  safe
 {
 (*func)();
 return ;
 }
 ).f_new` cannot access delegate `__lambda2` in frame of function 
 `onlineapp.f!(void function()  safe).f`
 onlineapp.d(13):        `__lambda2` declared here
 onlineapp.d(13): Error: template instance `onlineapp.f!(void 
 function()  safe).f.f_new!(delegate ()  safe
 {
 (*func)();
 return ;
 }
 )` error instantiating
 onlineapp.d(18):        instantiated from here: `f!(void function() 
  safe)`
 ```

 What confuses me a lot is that if I remove `template T` then 
 everything works perfectly:
The message is different, but I think this error is probably related to the "dual context" issue: https://issues.dlang.org/show_bug.cgi?id=5710 Basically, `f_new!(() => func())` has two scopes that it "wants" to be nested in: `T!int` and `f!(void function() safe)`. When you remove `template T`, there's only one scope, so it's not a problem.
No, there is no context pointer necessary for a template instantiation, without one being artificially introduced via an alias parameter. `T!int` is essentially just a namespace, especially since the `P` parameter isn't even used.
 
 If you remove `static` from `f_new`, you get an error message talking 
 about this explicitly:
Interesting that `static` does anything there, since it's a no-op. -Steve
Apr 19 2022
parent reply Andrey Zherikov <andrey.zherikov gmail.com> writes:
On Tuesday, 19 April 2022 at 16:38:42 UTC, Steven Schveighoffer 
wrote:
 On 4/19/22 11:46 AM, Paul Backus wrote:
 If you remove `static` from `f_new`, you get an error message 
 talking about this explicitly:
Interesting that `static` does anything there, since it's a no-op. -Steve
I put `static` because it fixes "dual context" error. Is there a way/workaround to achieve the desired behavior? Those two bugs pointed above were reported in 2017 and 2011 (!) which makes me think that they won't be fixed any time soon (I'm not sure that they are actually the same issue as I faced here).
Apr 19 2022
next sibling parent reply Andrey Zherikov <andrey.zherikov gmail.com> writes:
On Tuesday, 19 April 2022 at 18:18:26 UTC, Andrey Zherikov wrote:
 Is there a way/workaround to achieve the desired behavior? 
 Those two bugs pointed above were reported in 2017 and 2011 (!) 
 which makes me think that they won't be fixed any time soon 
 (I'm not sure that they are actually the same issue as I faced 
 here).
I tried to change the code to this: ```d void f(FUNC)(FUNC func) { enum dg = () => func(); T!int.f_new!dg; } ``` But I get this error: ``` onlineapp.d(7): Error: delegate `onlineapp.f!(void function() safe).f.__lambda2` is a nested function and cannot be accessed from `onlineapp.T!int.f_new!(delegate () safe { (*func)(); return ; } ).f_new` ```
Apr 19 2022
parent Andrey Zherikov <andrey.zherikov gmail.com> writes:
I get the same error even with just `struct`:
```d
struct S
{
     static void f_new(alias func)()
     {
         func();
     }
}

void f_new(alias func)()
{
     func();
}

void f(FUNC)(FUNC func)
{
     f_new!(() => func());   // works
     // S.f_new!(() => func());  // doesn't work
}
```
Apr 19 2022
prev sibling next sibling parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 4/19/22 11:18, Andrey Zherikov wrote:

 Is there a way/workaround to achieve the desired behavior?
Can you describe the goal a little more. For example, I assume you really need a more useful lambda although the example you show seems unnecessary: enum dg = () => func(); That lambda looks suspiciously trivial. Will it actually need its context? Ali
Apr 19 2022
parent Andrey Zherikov <andrey.zherikov gmail.com> writes:
On Tuesday, 19 April 2022 at 19:07:37 UTC, Ali Çehreli wrote:
 On 4/19/22 11:18, Andrey Zherikov wrote:

 Is there a way/workaround to achieve the desired behavior?
Can you describe the goal a little more. For example, I assume you really need a more useful lambda although the example you show seems unnecessary: enum dg = () => func(); That lambda looks suspiciously trivial. Will it actually need its context? Ali
I have [old API function](https://github.com/andrey-zherikov/argparse/blob/master/sour e/argparse.d#L1228) that gets a function as a parameter and I want it to call (for backward compatibility) [new API function](https://github.com/andrey-zherikov/argparse/blob/remove-main/sour e/argparse.d#L1630) which accepts a function as a template parameter.
Apr 19 2022
prev sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 4/19/22 2:18 PM, Andrey Zherikov wrote:
 On Tuesday, 19 April 2022 at 16:38:42 UTC, Steven Schveighoffer wrote:
 On 4/19/22 11:46 AM, Paul Backus wrote:
 If you remove `static` from `f_new`, you get an error message talking 
 about this explicitly:
Interesting that `static` does anything there, since it's a no-op.
I put `static` because it fixes "dual context" error.
So you saw a "dual context" error, and added static, and you got a different error? This is a bit telling. I think the compiler is maybe thinking that it is inside an aggregate, where it needs a context pointer, and it doesn't actually need one.
 
 Is there a way/workaround to achieve the desired behavior? Those two 
 bugs pointed above were reported in 2017 and 2011 (!) which makes me 
 think that they won't be fixed any time soon (I'm not sure that they are 
 actually the same issue as I faced here).
 
The dual-context problem is old, and has actually been solved, but then reverted because the mechanism used is not portable to LDC and GDC (i.e. some current code compiles on DMD, but not LDC/GDC). The other problem is just a straight-up bug. You can work around the dual context, if you are OK with passing the second context explicitly. The easiest way is to move the member function to a UFCS function. an example: ```d struct X { int x; void applyToX(alias fn)() {fn(x);} } void applyToX_alt(alias fn)(ref X xval) { fn(xval.x); } void main() { auto s = X(5); int y = 6; void fn(ref int x) { x += y; } s.applyToX!fn; // error, dual context needed s.applyToX_alt!fn; // fine, only single context needed } ``` You might ask, what is the drawback? I mean, it works exactly the same, same usage syntax. The difference is in what happens when you take a delegate of the function. `&s.applyToX!fn` is very much different than `&s.applyToX_alt!fn`. The latter isn't even possible. But most of the time, there is no intention to take a delegate, so you can get away with the UFCS version. -Steve
Apr 19 2022
parent reply Andrey Zherikov <andrey.zherikov gmail.com> writes:
On Tuesday, 19 April 2022 at 20:29:01 UTC, Steven Schveighoffer 
wrote:
 You can work around the dual context, if you are OK with 
 passing the second context explicitly.

 The easiest way is to move the member function to a UFCS 
 function. an example:

 ```d
 struct X
 {
    int x;
    void applyToX(alias fn)() {fn(x);}
 }

 void applyToX_alt(alias fn)(ref X xval) {
    fn(xval.x);
 }

 void main()
 {
    auto s = X(5);
    int y = 6;
    void fn(ref int x) { x += y; }
    s.applyToX!fn; // error, dual context needed
    s.applyToX_alt!fn; // fine, only single context needed
 }
 ```
I used struct to understand the problem. I don't actually have an object context to pass like in your example, the only context I have is template parameters.
Apr 19 2022
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 4/19/22 8:44 PM, Andrey Zherikov wrote:
 On Tuesday, 19 April 2022 at 20:29:01 UTC, Steven Schveighoffer wrote:
 You can work around the dual context, if you are OK with passing the 
 second context explicitly.

 The easiest way is to move the member function to a UFCS function. an 
 example:

 ```d
 struct X
 {
    int x;
    void applyToX(alias fn)() {fn(x);}
 }

 void applyToX_alt(alias fn)(ref X xval) {
    fn(xval.x);
 }

 void main()
 {
    auto s = X(5);
    int y = 6;
    void fn(ref int x) { x += y; }
    s.applyToX!fn; // error, dual context needed
    s.applyToX_alt!fn; // fine, only single context needed
 }
 ```
I used struct to understand the problem. I don't actually have an object context to pass like in your example, the only context I have is template parameters.
That's because your code is not an example of the dual context problem -- only one context is needed. Your code is triggering an actual bug in the compiler. -Steve
Apr 19 2022