【问题标题】:What are the differences between these 4 lambda expressions?这 4 个 lambda 表达式之间有什么区别?
【发布时间】:2019-03-29 21:17:12
【问题描述】:

我知道这对于 non-noob C++ 开发人员来说可能看起来很愚蠢,但是这 4 个 lambda 表达式之间有什么区别? Code:

#include <iostream>
#include <math.h>
#include <functional>

inline double MyFunction(double a, double b, double c) {
    return (a + b + c);
}

inline void FunctionWrapper(std::function<double(double)> tempFunct, double value) {
    std::function<double(double)> funct;

    funct = tempFunct;

    std::cout << "result: " << funct(value) << std::endl;
}

int main()
{    
    double value = 100.0;

    FunctionWrapper([](double value) { return MyFunction(value, 1.0, 2.0); }, value);
    FunctionWrapper([](double value) -> double { return MyFunction(value, 1.0, 2.0); }, value);

    FunctionWrapper([value](double value) { return MyFunction(value, 1.0, 2.0); }, value);
    FunctionWrapper([value](double value) -> double { return MyFunction(value, 1.0, 2.0); }, value);
}

好像也一样?使用两种不同的“符号”并将值用作闭包?

【问题讨论】:

  • 顺便说一句,所有的 lambda 表达式都彼此不同。即使auto a = [](){};auto b = [](){}; 也不一样,因为它们的类型不同
  • 第三个和第四个变种是变量阴影的情况:godbolt.org/z/GlUZbu。这就是为什么最好将所有警告和警告都启用为错误。
  • @Trass3r:根据 C++17,它们实际上格式不正确:stackoverflow.com/questions/52947934/…
  • 我知道。它仍然在阴影中。现在也被clang拒绝了。

标签: c++ lambda anonymous-function


【解决方案1】:

在这种情况下,它们都产生相同的结果。但是,它们之间存在逻辑上的差异。

  • [](double value) { return MyFunction(value, 1.0, 2.0); }

    这是一个 lambda,它采用 value 类型的单个参数并将其传递给 MyFunction。其返回类型由return 语句推导出为MyFunction 的返回类型,即double.

  • [](double value) -&gt; double { return MyFunction(value, 1.0, 2.0); }

    这与之前的 lambda 相同,但这次它的返回类型明确指定为 double。在这种情况下是一样的,但如果MyFunction 的返回类型是别的东西,它会与第一个不同。在这种情况下,第一个将返回 MyFunction 返回的内容,而这个仍将返回 double

  • [value](double value) { return MyFunction(value, 1.0, 2.0); }

    这取决于使用的标准版本。在 C++11 和 14 中,这个捕获了main 的局部变量value。但是,该捕获被 lambda 的参数 value 隐藏,因此它实际上是无用的。如果将 lambda 声明为例如[value](double v) { return MyFunction(value, 1.0, 2.0); }。这将传递捕获的value,而不是其参数。

    在 C++17 及更高版本中,这已更改,实际上格式不正确(编译错误)。不再允许将 lambda 参数命名为与您捕获的内容相同的名称。

    由于更改是一个缺陷报告 (CWG 2211),它具有追溯性,因此编译器即使在早期的 C++ 版本中拒绝此类代码也是合法的。

  • [value](double value) -&gt; double { return MyFunction(value, 1.0, 2.0); }

    与 lambda 数字 3 相同,具有明确的返回类型规范(3 和 4 之间的差异与 1 和 2 之间的差异完全相同)。

【讨论】:

  • 似乎 gcc 仍然接受格式错误的 lambda。但后来我看到 gcc 比标准的 VS 宽松得多。
  • 嗯,更改是由 CWG 2211 进行的,这是一个 DR,这意味着它也适用于旧标准。 (但编译器可能需要时间来赶上。)
  • @Angew: 所以写[value](double v) { return MyFunction(value, 1.0, 2.0); } 会被转换成{ double value = externalvalue; return MyFunction(value, 1.0, 2.0); }
  • @markzzz 差不多。更具体地说,就像任何其他捕获一样,value 将成为闭包类型的成员,并使用捕获的变量 value 进行初始化。
  • @markzzz 这是一个单独的问题,应该用自己的研究来处理(建议的起点:cppreference on lambdas),如果证明不够,一个新的 SO 问题。
【解决方案2】:

第二个 lambda 与第一个的不同之处在于您明确指定了返回类型。由于第一个 lambda 推导出的返回类型相同,所以没有区别。

第三个和第四个 lambda 格式不正确,因为它们声明了一个与捕获同名的参数。见标准规则:

[expr.prim.lambda.capture]

如果简单捕获中的标识符作为 lambda-declarator 的 parameter-declaration-clause 的参数的 declarator-id 出现,则程序格式错误。 [ 示例:

void f() {
  int x = 0;
  auto g = [x](int x) { return 0; }    // error: parameter and simple-capture have the same name
}

— 结束示例 ]

这个措辞在 C++17 中被采用。

【讨论】:

    【解决方案3】:

    第一个 lambda 是常用的。

    第二个 lambda 表示它的返回类型是double

    最后两个 lambda 表达式不正确。 value 被值和它的参数捕获,不应该尝试(有一个警告表明捕获不工作)。要么是参数,要么是捕获的变量(按值或引用)。

    我想,它们应该是这样的:

    [foo](double value) { return MyFunction(value, 1.0, foo); }, value);
    

    【讨论】:

    • 编译器(如 OP 提供的链接所示)在第三点上不同意您的观点。
    • "警告:未使用 lambda capture 'value' [-Wunused-lambda-capture]",不是错误。
    • 改变了我的意思,它仍然不是声音代码。警告是一个很好的指标,谢谢!
    • @LightnessRacesinOrbit: C++17 ? gcc/clang 都接受它Demo。 (甚至 c++2a Demo
    【解决方案4】:

    第一个和第二个 lambda 以及第三个和第四个 lambda 之间的区别在于,您要么明确指定返回类型,要么不明确指定返回类型。在这里它会产生相同的功能。

    前两个 lambdas 与 lambdas 3 和 4 之间的区别在于捕获。但是您的示例不适合说明捕获的效果。看下面的代码(live demo)。

    int main()
    {    
        double v = 100.0;
    
        auto lam1= [](double val) { return MyFunction(val, 1.0, 2.0); }; //A
        auto lam2= [v](double val) { return MyFunction(v, 1.0, 2.0); }; //B
        auto lam3= [&v](double val) { return MyFunction(v, 1.0, 2.0); }; //C
    
        FunctionWrapper( lam1, v++ ); //1
        FunctionWrapper( lam2, v++ ); //2
        FunctionWrapper( lam3, v++ ); //3
        std::cout << "v: " << v << '\n';
    }
    

    虽然lam1 不捕获任何内容,但lam2 按值捕获vlam3 通过引用捕获v。三个FunctionWrapper 调用的输出分别为 103、103、106。这是因为虽然v//1 行中发生了更改,但它的旧值在//2 行中使用。也就是说,按值捕获意味着vlam2 在行中初始化时保持的值//B 存储在lam2 中。另一方面,在//3 行中使用v 的当前值。这是因为lam3 持有对v 的引用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-09-22
      • 2021-05-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-03-17
      相关资源
      最近更新 更多