使用 lambda 调用时编译时折叠会导致错误

Compile-time fold results in error when called with a lambda

我试图在编译时 fold 一个数组,并将结果存储在 enum 中。当枚举(和对 fold 的调用)处于模块级别时它工作得很好,但是当它都包含在使用 lambda 调用的结构 中时编译失败。

下面是一些失败代码的简单示例:

import std.algorithm.iteration;
import std.stdio;

struct Foo
{
    // Version 1 (works)
    //enum x = [ 1, 2, 3 ].fold!"a * b";

    // Version 2 (works)
    //enum x = [ 1, 2, 3 ].fold!mult;

    // Version 3 (broken)
    enum x = [ 1, 2, 3 ].fold!((a, b) => a * b);


    pragma(msg, "x = ", x);
}

// Outside of the struct, it works
enum y = [ 1, 2, 3 ].fold!((a, b) => a * b);
pragma(msg, "y = ", y);

int mult(int a, int b)
{
    return a * b;
}

void main(){}

(1、2版本,被注释掉了,编译正常,只是3版本有问题。)

编译时出现如下错误:

C:\D\dmd2\windows\bin\..\..\src\phobos\std\algorithm\iteration.d(3690): Error: `this.__lambda2` has no value
C:\D\dmd2\windows\bin\..\..\src\phobos\std\algorithm\iteration.d(3690):        while evaluating: `static assert(((int)).length == fun.length)`
C:\D\dmd2\windows\bin\..\..\src\phobos\std\algorithm\iteration.d(3697): Error: `this.__lambda2` has no value
C:\D\dmd2\windows\bin\..\..\src\phobos\std\algorithm\iteration.d(3718): Error: `this.__lambda2` has no value
C:\D\dmd2\windows\bin\..\..\src\phobos\std\algorithm\iteration.d(3636): Error: template instance `broken.Foo.reduce!((a, b) => a * b).reduceImpl!(false, int[], int)` error instantiating
C:\D\dmd2\windows\bin\..\..\src\phobos\std\algorithm\iteration.d(4086):        instantiated from here: `reduce!(int[])`
.\broken.d(13):        instantiated from here: `fold!(int[])`
x = .\broken.d(13): Error: CTFE failed because of previous errors in `fold`
.\broken.d(16):        while evaluating `pragma(msg, x)`
y = 6
Failed: ["C:\D\dmd2\windows\bin\dmd.exe", "-v", "-o-", ".\broken.d", "-I."]

我尝试查看错误中提到的源代码,但其中使用的概念超出了我目前对 D 的了解。

我最初假设 lambda 表达式在编译时可能无法正常工作,但 enum y 计算正确,所以我猜这不是...

我正在使用 DMD v2.086.1,但在使用 LDC 1.16.0 和 1.14.0 时遇到了同样的问题。

这是因为编译器不太擅长确定 lambda 是否需要访问其上下文。如果你写了这样的东西:

struct S {
    int n;
    int fun() {
        import std.algorithm.iteration : fold;
        return [1,2,3].fold!((a,b) => a*b*n);
    }
}

应该很清楚,上面的lambda需要访问结构中的n。以同样的方式,编译器在 enum x = [ 1, 2, 3 ].fold!((a, b) => a * b); 的警告方面犯了错误,并假设 Foo 中存在一些会影响计算结果的状态。归档为 issue 20077.

您已经找到了一些变通方法,还有一个值得一提——向 lambda 添加参数类型:

enum x = [ 1, 2, 3 ].fold!((int a, int b) => a * b);

这样,编译器可以更早地找出 lambda 需要的信息,并且能够确定它不需要访问周围的范围。