www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - opApply seems like it can infer delegate types AND parameters!?

reply Quirin Schroll <qs.il.paperinik gmail.com> writes:
In an attempt to come up with an [answer to a 
post](https://forum.dlang.org/post/dnsuyvnfcszwefsfzcze forum.dlang.org), I
found something odd, but useful, that I nonetheless don’t understand.

As far as I know – or rather thought I knew – `foreach` loops can 
infer the types of the “loop variables” if an `opApply` is a 
function, not a function *template,* and the number and ref-ness 
of loop variables, as well as the function attributes, 
disambiguate the overload.

What seems to be possible, and always was since version 2.060, is 
actually combining the two:
1. Instead of implementing a function `opApply(scope int 
delegate(...))`, write a function template `opApplyImpl(DG)(scope 
int delegate(...))` (or whatever name) and let it take the 
delegate type as a template type parameter.
2. Make `opApply` an alias to an instance of the template, 
passing the desired delegate type as an argument.

You can even do multiple templates or alias different instances 
to the same template.

I always thought you had to provide aliases with all 16 
combinations of the attributes ` safe`, ` nogc`, `pure`, and 
`nothrow` for each actually desired instance. But you don’t and 
**I have no clue why**.

Why does it work?

Because the *Shorten* on run.dlang.io doesn’t seem to work, 
here’s the full code:
```d
struct WithIndexType(T, U)
{
     U[] array;

     int opApplyImpl(DG)(scope DG callback)
     {
         pragma(msg, "opApplyImpl(", DG, ")");
         for (T index = 0; index < cast(T)array.length; ++index)
         {
             import std.traits : Parameters;
             static if (Parameters!DG.length == 1)
             {
                 if (auto result = callback(array[index])) return 
result;
             }
             else static if (Parameters!DG.length == 2)
             {
                 if (auto result = callback(index, array[index])) 
return result;
             }
             else
             {
                 static assert(0, "DG is not a callable type");
             }
         }
         return 0;
     }
     alias opApply = opApplyImpl!(int delegate(T, ref U));
     alias opApply = opApplyImpl!(int delegate(ref U));
}

auto withIndexType(T, U)(return scope U[] values)  safe pure 
nothrow  nogc
{
     return WithIndexType!(T, U)(values);
}

void main()  safe
{
     import std.stdio;

     double[] xs = new double[](20);
     foreach (i, ref d; xs.withIndexType!byte)
     {
         static assert(is(typeof(i) == byte));
         static assert(is(typeof(d) == double));
         d = i + 1;
     }
     foreach (d; xs.withIndexType!byte)
     {
         static assert(is(typeof(d) == double));
         write(d, ' ');
     }
}
```
The pragma shows that the template is being instantiated with the 
actual types (in terms of attributes and ref-ness) of the 
generated closure.
```
opApplyImpl(int delegate(byte, ref double))
opApplyImpl(int delegate(ref double))
opApplyImpl(int delegate(byte, ref double) pure nothrow  nogc 
 safe)
opApplyImpl(int delegate(ref double)  safe)
```

If you don’t use a template and aliased instance, i.e. you just 
use the following, you get errors because of attributes:
```d
     int opApply(scope int delegate(T, ref U) callback)
     {
     	for (T index = 0; index < cast(T)array.length; ++index)
         {
         	if (auto result = callback(index, array[index])) return 
result;
         }
         return 0;
     }

     int opApply(scope int delegate(ref U) callback)
     {
         for (T index = 0; index < cast(T)array.length; ++index)
         {
             if (auto result = callback(array[index])) return 
result;
         }
         return 0;
     }
```
```
Error: ` safe` function `D main` cannot call ` system` function 
`onlineapp.WithIndexType!(byte, double).WithIndexType.opApply`
         which wasn't inferred ` safe` because of:
        ` safe` function `opApply` cannot call ` system` `callback`
        `onlineapp.WithIndexType!(byte, 
double).WithIndexType.opApply` is declared here
```
(The error appears twice as `main` contains two loops.)
I would have expected this error regardless whether the called 
`opApply` is a function or an aliased function template instance, 
but apparently, it makes a difference.

The fact that the enclosing struct is a template doesn’t affect 
it either.
Dec 11 2023
next sibling parent Quirin Schroll <qs.il.paperinik gmail.com> writes:
On Monday, 11 December 2023 at 23:21:45 UTC, Quirin Schroll wrote:
 […]
 1. Instead of implementing a function `opApply(scope int 
 delegate(...))`, write a function template 
 `opApplyImpl(DG)(scope int delegate(...))` (or whatever name) 
 and let it take the delegate type as a template type parameter.
[Correction] This should have been: 1. Instead of implementing a function `opApply(scope int delegate(...))`, write a function template **`opApplyImpl(DG)(scope DG)`** (or whatever name) and let it take the delegate type as a template type parameter.
Dec 11 2023
prev sibling parent Paul Backus <snarwin gmail.com> writes:
On Monday, 11 December 2023 at 23:21:45 UTC, Quirin Schroll wrote:
 I always thought you had to provide aliases with all 16 
 combinations of the attributes ` safe`, ` nogc`, `pure`, and 
 `nothrow` for each actually desired instance. But you don’t and 
 **I have no clue why**.

 Why does it work?
Truly bizarre. I can't think of any explanation other than a compiler bug. Probably not a good idea to rely on this, even if the result is pretty handy.
Dec 11 2023