D如何允许代理作为模板参数?

dsp*_*pyz 4 templates delegates d stack-frame

在Andrei Alexandrescu的"D编程语言"中,

这是一个将委托作为模板参数的示例:

T[] find(alias pred, T)(T[] input)
  if(is(typeof(pred(input[0])) == bool))
{
  for(; input.length > 0; input = input[1 .. $]) {
    if (pred(input[0])) break;
  }
  return input;
}

unittest {
  int[] a = [1,2,3,4,-5,3,-4];
  int z = -2;
  auto b = find!(delegate(x) { return x < z; })(a);
  asssert(b == a[4..$]);
}
Run Code Online (Sandbox Code Playgroud)

Alexandrescu解释说这是有效的,因为委托实际上是一个胖指针,由两部分组成:函数指针和指向其堆栈框架的指针(这就是为什么z可以在其体内访问).除了find将"pred"作为TEMPLATE参数,而不是作为参数.模板参数只能是编译时常量.

我确定单元测试中匿名委托的地址确实是一个编译时常量,但它的堆栈帧的地址当然不应该,所以如何将委托作为模板参数?

这里到底发生了什么?

Ada*_*ppe 7

别名参数生成为给定的特定符号定制的新代码,其中包括上下文中的内容.

我们来看看反汇编:

0805c850 <_D6test564mainFZv20__T12__dgliteral1TiZ12__dgliteral1MFNbNfiZb>:
805c850:       55                      push   ebp
805c851:       8b ec                   mov    ebp,esp
805c853:       83 ec 04                sub    esp,0x4
805c856:       8b 48 d8                mov    ecx,DWORD PTR [eax-0x28]
805c859:       3b 4d 08                cmp    ecx,DWORD PTR [ebp+0x8]
805c85c:       0f 9f c0                setg   al
805c85f:       0f b6 c0                movzx  eax,al
805c862:       c9                      leave
805c863:       c2 04 00                ret    0x4
Run Code Online (Sandbox Code Playgroud)

这是在这里创建的文字委托(没有优化btw).有趣的线条是中间的mov和cmp.

请注意,上下文指针将传递给eax寄存器中的委托.让我们看看这个叫做的地方:

0805c868 <_D6test5632__T4findS18main12__dgliteral1TiZ4findMFNaNbNfAiZAi>:
 // snip a bunch of irrelevant code
805c870:       89 45 fc                mov    DWORD PTR [ebp-0x4],eax
// snip
805c892:       8b 45 fc                mov    eax,DWORD PTR [ebp-0x4]
805c895:       89 95 f8 ff ff ff       mov    DWORD PTR [ebp-0x8],edx
805c89b:       e8 b0 ff ff ff          call   805c850 <_D6test564mainFZv20__T12__dgliteral1TiZ12__dgliteral1MFNbNfiZb>
Run Code Online (Sandbox Code Playgroud)

注意那里有两件事:第一,名字:注意dgliteral在那里 - 这是这个特定参数的特殊生成函数!

其次,请注意在eax中传递给此函数的任何内容都会被存储并最终传递给另一个函数.

让我们再次上调调用堆栈,现在我们在_Dmain中出现查找调用:

 805c7da:       89 e8                   mov    eax,ebp
 805c7dc:       e8 87 00 00 00          call   805c868 <_D6test5632__T4findS18main12__dgliteral1TiZ4findMFNaNbNfAiZAi>
Run Code Online (Sandbox Code Playgroud)

它正在传递基指针!顺便说一下,还记得0x28吗?我们也可以在_Dmain中看到它,有几行:

 805c7d1:       c7 45 d8 fe ff ff ff    mov    DWORD PTR [ebp-0x28],0xfffffffe
Run Code Online (Sandbox Code Playgroud)

这是int z = -2;行(-2表示为32位的fffffffe).它像常规局部变量一样存储在堆栈中.

最重要的是,特定的别名参数会生成一个全新的函数,该函数知道找到所有局部变量的位置.调用它时,会将指向它们的基指针转发给它,这是它需要知道的全部内容.

请注意,你也可以将局部变量作为别名参数传递并获得类似的代码,它会产生一个直接戳偏移而不是指针的函数.

此外,如果您尝试将运行时委托传递给alias参数,或者尝试将别名dg存储在其他位置,那么将无法编译.这是一个特殊的函数,具有特定于案例的代码,很多泛型委托的东西并不适合它.