In an attempt to come up with an [answer to a post](https://forum.dlang.org/post/dnsuyvnfcszwefsfz...@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.

Reply via email to