1

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

// 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; });

闭包对象由其类型对应于 lambda 表达式vglambda的闭包对象初始化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[在本质上似乎是printerusing的调用operator ()]

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

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

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

任何专家都可以提出更多的观点吗?

4

1 回答 1

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是使用. 使用接收泛型 ( ) 值 ( ) 的泛型 lambda 函数进行初始化std::coutvglambdaautoprinter

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

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

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

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

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

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

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

在这种情况下,&&不是右值引用而是转发 引用(有关更多信息,请参阅此页面),正如您从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使用在初始化的 lambda 中定义的泛型和可变参数 lambda 进行初始化vglambda。所以什么时候被调用

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

你知道通用可变参数 lambda 是用ts...扩展为1和的可变参数包调用'a'3.14

所以,调用p(1, 'a', 3.14),你有调用(忽略转发部分)

printer(1, 'a', 3.14);

(其中printer()的 lambda 是std::cout abc并且返回的[=] { printer(1, 'a', 3.14); }

Soq[=] { printer(1, 'a', 3.14); }and 初始化,调用

q();

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

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

因此,从p(1, 'a', 3.14)您激活第一个print()(带有 的那个std::forward),每次调用返回值(q在您的示例中)时,您都会激活第二个print()(没有 的那个std::forward)。

但是你的代码有一个很大的缺陷。p()一个缺陷,在调用原始类型时不会产生问题,如int,chardouble. 但是使用支持移动语义的复杂对象是一个危险的缺陷。

问题是,使用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...); };
};
于 2019-09-29T12:08:17.913 回答