digitalmars.D.learn - opApply seems like it can infer delegate types AND parameters!?
- Quirin Schroll (122/122) Dec 11 2023 In an attempt to come up with an [answer to a
- Quirin Schroll (6/11) Dec 11 2023 [Correction] This should have been:
- Paul Backus (4/9) Dec 11 2023 Truly bizarre. I can't think of any explanation other than a
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
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
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