opApply seems like it can infer delegate types AND parameters!?

Quirin Schroll qs.il.paperinik at gmail.com
Mon Dec 11 23:21:45 UTC 2023


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.


More information about the Digitalmars-d-learn mailing list