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