【问题标题】:Making sense of nested lambda expression理解嵌套的 lambda 表达式
【发布时间】:2019-09-29 03:49:10
【问题描述】:

我的问题与以下嵌套的 lambda 表达式有关,在 Lambda expressions 下作为示例提供

// generic lambda, operator() is a template with one parameter
auto vglambda = [](auto printer) {
    return [=](auto&&... ts) // generic lambda, ts is a parameter pack
    { 
        printer(std::forward<decltype(ts)>(ts)...);
        return [=] { printer(ts...); }; // nullary lambda (takes no parameters)
    };
};
auto p = vglambda([](auto v1, auto v2, auto v3) { std::cout << v1 << v2 << v3; });
auto q = p(1, 'a', 3.14); // outputs 1a3.14
q();                      // outputs 1a3.14

以下是我对上述表达式的解读方式:

在表达式中

auto p = vglambda([](auto v1, auto v2, auto v3) { std::cout << v1 << v2 << v3; });

闭包对象vglambda被初始化为闭包对象printer,其类型对应于lambda表达式

[](auto v1, auto v2, auto v3) { std::cout << v1 << v2 << v3; }

printer 中,嵌套的(匿名)lambda 表达式

return [=](auto&&... ts){}

通过复制捕获printer 及其参数包作为rvalue 引用。

在(匿名)lambda 表达式的主体中,表达式

printer(std::forward<decltype(ts)>(ts)...);

将参数包转发到printer [在本质上似乎是使用operator () 调用printer]

在(匿名)lambda 表达式主体内的最终表达式中,(匿名)空值 lambda 表达式似乎通过复制从封闭范围捕获 printer 闭包对象以及参数包,并调用printer 闭包对象及其转发参数包。

return [=] { printer(ts...); };

现在,很明显我在这里没有得到任何东西。本质上,为什么在(匿名)lambda 表达式的主体中提供了两条不同的调用 printer 闭包对象的行,一条没有(匿名)空值 lambda 表达式,另一条在内部?

有哪位专家可以提出更多观点吗?

【问题讨论】:

  • 您的代码使用了从 C++14 引入的通用 lambda;所以我在 C++14 中更改了你的 C++11 标签

标签: c++ lambda c++14 variadic-templates generic-lambda


【解决方案1】:

在表达式中

auto p = vglambda([](auto v1, auto v2, auto v3) { std::cout << v1 << v2 << v3; });

闭包对象vglambda被初始化为闭包对象打印机,其类型对应于lambda表达式

[](auto v1, auto v2, auto v3) { std::cout << v1 << v2 << v3; }

在我看来更正确的说法是vglamba调用 使用std::cout 的lambda。 vglambda 使用通用 lambda 函数初始化,该函数接收通用 (auto) 值 (printer)。

printer 内,嵌套的(匿名)lambda 表达式

return [=](auto&&... ts){}

通过复制捕获打印机及其参数包作为右值引用。

不在printer 内部(这只是 lambda 的参数),而是在保存在 vglambda 变量中的 lambda 内部。

是的,匿名嵌套泛型和可变参数函数按值捕获 printer,但并不精确地捕获 ... ts(是参数)和右值引用。

嵌套的 lambda 几乎等同于模板函数(嗯...相当于一个内部带有模板 operator() 的结构...但为了使其更简单...)

template <typename ... Ts>
auto func (Ts && ... ts)
 { /*...*/ } 

在这种情况下,&amp;&amp; 不是右值引用,而是 forwarding references(有关更多信息,请参阅this page),正如您从内部使用中看到的那样std::forward.

这是很重要的一点,但请看下一个。

在(匿名)lambda 表达式的主体内,表达式

printer(std::forward<decltype(ts)>(ts)...);

将参数包转发到printer [在本质上似乎是使用operator () 调用打印机]

这在我看来是正确的。

在(匿名)lambda 表达式主体内的最终表达式中,(匿名)空值 lambda 表达式似乎通过复制从封闭范围捕获 printer 闭包对象以及参数包,并调用printer 闭包对象及其转发参数包。

return [=] { printer(ts...); };

这在我看来是正确的,但您应该在这里看到一个问题(连同前面的 printer() 调用。

为什么在(匿名)lambda 表达式的主体中提供了两条不同的调用打印机闭包对象的行,一条没有(匿名)空值 lambda 表达式,另一条在内部?

看看p是如何使用的

auto p = vglambda([](auto v1, auto v2, auto v3) { std::cout << v1 << v2 << v3; });
auto q = p(1, 'a', 3.14);

q();   

p是如何初始化的?

p 使用在初始化vglambda 的 lambda 中定义的通用和可变参数 lambda 进行初始化。所以什么时候调用

auto q = p(1, 'a', 3.14);

您已经使用扩展为 1'a'3.14 的可变参数包 ts... 调用了通用可变参数 lambda。

所以,调用p(1, 'a', 3.14),你已经调用了它(忽略转发部分)

printer(1, 'a', 3.14);

(其中printer()std::coutabc 的lambda)返回[=] { printer(1, 'a', 3.14); }

所以q 被初始化为[=] { printer(1, 'a', 3.14); } 并调用

q();

printer(1, 'a', 3.14) 再次被调用。

因此,泛型和可变参数 lambda 的想法是调用 print(),接收可变参数后,第一次返回另一个 lambda,在调用时再次返回 print()

所以从p(1, 'a', 3.14) 激活第一个print()(带有std::forward 的那个),每次调用返回值(q,在你的例子中)你激活第二个print()(那个没有std::forward)。

但是你的代码有一个很大的缺陷。使用基本类型为intchardouble 调用p() 时不会出现问题的缺陷。但是使用支持移动语义的复杂对象是一个危险的缺陷。

问题在于,使用std::forward,您可以激活移动语义。

所以在这段代码中

return [=](auto&&... ts) // generic lambda, ts is a parameter pack
{ 
    printer(std::forward<decltype(ts)>(ts)...);
    return [=] { printer(ts...); }; // <- DANGER: unsafe use of `ts...`
};

第一次调用printer() 是安全且正确的(使用std::forward),但第二次调用printer() 很危险,因为我们不知道ts... 是否仍然可用。

使用ts...两次次,我建议重写lambda如下

return [=](auto const & ... ts)
{ 
    printer(ts...);
    return [=] { printer(ts...); };
};

【讨论】:

    猜你喜欢
    • 2016-07-13
    • 1970-01-01
    • 2014-11-28
    • 1970-01-01
    • 1970-01-01
    • 2015-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-02
    相关资源
    最近更新 更多