理解嵌套的 lambda 表达式

Making sense of nested lambda expression

我的问题与以下嵌套 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用类型对应lambda表达式

的闭包对象printer初始化
[](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 表达式主体内的最终表达式中,(匿名)nullary lambda 表达式似乎通过复制从封闭范围捕获 printer 闭包对象以及参数包,并使用其转发的参数包调用 printer 闭包对象。

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

现在,很明显我没有得到正确的结果。本质上,为什么在(匿名)lambda 表达式的主体中提供了两行不同的调用 printer 闭包对象的行,一个没有(匿名)nullary lambda 表达式,一个在内部?

有哪位专家能提供更多信息吗?

In the expression

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

the closure object vglambda is initialized with the closure object printer whose type corresponds to the lambda expression

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

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

Inside printer, the nested (anonymous) lambda expression

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

captures printer by copy and its parameter pack as rvalue reference.

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

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

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

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

在这种情况下,&& 不是右值引用,而是 转发 引用(参见 this page更多信息),正如您从内部使用 std::forward.

看到的那样

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

Inside the body of the (anonymous) lambda expression, the expression

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

forwards the parameter pack to printer [in what essentially appears to be an invocation of printer using operator ()]

这在我看来是正确的。

In the final expression inside the body of the (anonymous) lambda expression, the (anonymous) nullary lambda expression appears to capture the printer closure object from the enclosing scope by copy, along with the parameter pack, and invokes the printer closure object with its forwarded parameter pack.

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

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

why are two distinct lines of invoking the printer closure object provided within the body of the (anonymous) lambda expression, one without the (anonymous) nullary lambda expression, and one within?

看看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::cout abc 的 lambda) 即 returned [=] { printer(1, 'a', 3.14); }.

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

q();

printer(1, 'a', 3.14)又被调用了。

所以通用和可变参数 lambda 的想法是调用 print(),第一次收到可变参数, return 另一个 lambda调用时再次 print()

所以从 p(1, 'a', 3.14) 开始,您激活第一个 print()(带有 std::forward 的那个)并且每次调用 returned 值(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...); };
};