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