opApply Magic Function Body Transformation
kinke
noone at nowhere.com
Mon Jul 28 12:25:39 UTC 2025
On Monday, 28 July 2025 at 04:39:00 UTC, Mike Shah wrote:
> **So really my one concrete question is** -- can I see
> main.main()__foreachbody_L21_C3(ref int) anywhere?
I think that's where the confusion comes from, that misleading
`-vcg-ast` output for the loop-body-lambda, apparently printed as
`… => 0` regardless of the actual loop body.
Based on your example:
```d
import core.stdc.stdio;
struct Array(T) {
T[] array;
int opApply(scope int delegate(ref T) dg){
foreach (ref i; array){
const result = dg(i);
if (result)
return result;
}
return 0;
}
}
void main() {
Array!int ints;
ints.array = [4,6,8,10,12];
foreach (item; ints) {
if (item == 1)
continue;
if (item == 2)
break;
printf("%d\n", item);
}
}
```
The loop is actually rewritten by the compiler to:
```d
ints.opApply((ref int item) {
if (item == 1)
return 0; // continue => abort this iteration
if (item == 2)
return 1; // break => abort this and all future iterations
printf("%d\n", item);
return 0; // continue with next iteration
});
```
So the main thing here is that the body is promoted to a lambda,
and the control-flow statements inside the body (`break` and
`continue` in the example above) are transformed to specific
return codes for the opApply delegate protocol.
If we add a `return` statement to the body:
```d
int main() {
Array!int ints;
ints.array = [4,6,8,10,12];
foreach (item; ints) {
if (item == 0)
return item;
if (item == 1)
continue;
if (item == 2)
break;
printf("%d\n", item);
}
return 0;
}
```
then the rewrite becomes a bit more complex:
```d
int __result; // magic variable inserted by the compiler, for
the main() return value
const __opApplyResult = ints.opApply((ref int item) {
if (item == 0) {
__result = item; // set return value for parent function
return 2; // return => abort the loop and exit from parent
function
}
if (item == 1)
return 0; // continue => abort this iteration
if (item == 2)
return 1; // break => abort this and all future iterations
printf("%d\n", item);
return 0; // continue with next iteration
});
switch (__opApplyResult) {
default:
break;
case 2:
return __result;
}
return __result = 0;
```
> **A third question** Will this call to a delegate provide more
> hidden allocations I wonder?
As with any regular lambda, captured outer variables (like the
`__result` in the 2nd example) will cause a closure, but as long
as the `opApply` takes the delegate as `scope`, the closure will
be on the stack, so no harm.
More information about the Digitalmars-d-learn
mailing list