In an attempt to come up with an answer to a post, 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:
- Instead of implementing a function
opApply(scope int delegate(...))
, write a function templateopApplyImpl(DG)(scope int delegate(...))
(or whatever name) and let it take the delegate type as a template type parameter. - 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:
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:
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.