lambda可以转化为函数吗?

Nik*_*iou 14 c++ lambda c++14

众所周知,lambda函数是引擎盖下的函子.

这段视频中(@约45:43)Bjarne说:

我提到lambda会转换为函数对象,如果方便的话会转换为函数

我可以看到这是一个编译器优化(即它不会改变lambda作为未命名的仿函数的感知,这意味着例如lambda仍然不会重载)但是有没有规定何时适用?

编辑

我理解术语翻译的方式(这就是我所要求的)与转换无关(我不会问lambdas是否可以转换为函数ptr等).通过翻译我的意思是"将lambda表达式编译成函数而不是函数对象".

cppreference中所述: lambda表达式构造一个未命名的prvalue临时对象,该对象具有唯一的非命名非联合非聚合类型,称为闭包类型.

问题是:这个对象可以被省略并且具有简单的功能吗?如果是,那么何时以及如何?


注意:我想象一个这样的规则是"不捕获任何东西",但我找不到任何可靠的来源来证实它

mel*_*k47 7

来自Lambda表达式 §5.1.2p6(草案N4140)

没有lambda-capture的非泛型lambda表达式的闭包类型具有公共非虚拟非显式const转换函数,指向函数,C++语言链接具有与闭包类型函数相同的参数和返回类型呼叫运营商.

  • 但它与任何合理的人类需要得到的一样接近. (8认同)

ala*_*ain 7

标准报价已经发布,我想补充一些例子.只要没有捕获的变量,就可以将lambda指定给函数指针:

法律:

int (*f)(int) = [] (int x) { return x + 1; };  // assign lambda to function pointer
int z = f(3);  // use the function pointer
Run Code Online (Sandbox Code Playgroud)

非法:

int y = 5;
int (*g)(int) = [y] (int x) { return x + y; };  // error
Run Code Online (Sandbox Code Playgroud)

法律:

int y = 5;
int z = ([y] (int x) { return x + y; })(2);  // use lambda directly
Run Code Online (Sandbox Code Playgroud)

(编辑)
因为我们不能向Bjarne询问他的意思,我想尝试一些解释.

"翻译"意思是"转换"
这是我最初的理解,但现在很清楚,问题不在于这个可能的含义.

C++标准中使用的"translate",意思是"编译"(或多或少)
正如Sebastian Redl已经评论过的那样,二进制级别上没有函数对象.只有操作码和数据,标准不讨论或指定任何二进制格式.

"translate"意思是"在语义上等价"
这意味着如果A和B在语义上是等价的,那么A和B产生的二进制代码可以是相同的.我的其余部分使用了这种解释.

一个封闭由两个部分组成:

  • lambda体中的语句,"代码"
  • 捕获的变量值或引用,"数据"

这相当于一个仿函数,如问题中所述.

函数可以被视为对象的子集,因为它们具有代码和数据,但只有一个成员函数:调用运算符.因此,闭包可以被视为在语义上等同于受限形式的对象.

一个功能,另一方面,没有与之相关的数据.当然有参数,但这些参数必须由调用者提供,并且可以从一个调用更改为另一个调用.这是对闭包的语义差异,其中绑定变量不能被更改并且不由调用者提供.

成员函数不是独立的东西,因为它没有它的对象就无法工作,所以我认为这个问题指的是一个独立的函数.

所以不,lambda在语义上通常不等同于函数.

有一个明显的特殊情况,一个没有捕获变量的lambda,其中functor只包含代码,这相当于一个函数.

但是,lambda可以说在语义上等同于一函数.每个可能的闭包(绑定变量的值/引用的不同组合)将等同于该集合中的一个函数.当然,只有当绑定变量只能有一组非常有限的值/只引用几个不同的变量(如果有的话)时,这才有用.
例如,我认为没有理由为什么编译器不能将以下两个片段视为(几乎*)等效:

void Test(bool cond, int x)
{
    int y;
    if(cond) y = 5;
    else y = 3;
    auto f = [y](int x) { return x + y; };
    // more code that
    // uses f
}
Run Code Online (Sandbox Code Playgroud)

一个聪明的编译器可以看到y只能有值5或3,并编译好像它会像这样写:

int F1(int x)
{
    return x + 5;
}

int F2(int x)
{
    return x + 3;
}

void Test(bool cond, int x)
{
    int (*f)(int);
    if(cond) f = F1;
    else f = F2;
    // more code that
    // uses f
}
Run Code Online (Sandbox Code Playgroud)

(*)当然这取决于究竟more code that uses f是什么.

另一个(可能更好)的例子是lambda,它总是通过引用绑定相同的变量.然后,只有一个可能的闭包,因此它等效于函数,如果函数通过其他方式访问该变量而不是将其作为参数传递.


另一个可能有用的观察是询问

这个对象[闭包]可以被省略并且具有简单的功能吗?如果是,那么何时以及如何?

与询问何时以及如何在没有该对象的情况下使用成员函数或多或少相同.由于lambdas是仿函数,而仿函数是对象,因此这两个问题密切相关.lambda的绑定变量对应于对象的数据成员,lambda body对应于成员函数的主体.


Mik*_*sev 6

TLDR:如果你只使用lambda将它转换为函数指针(并且只通过该函数指针调用它),那么省略闭包对象总是有利可图的.实现这一点的优化是内联和消除代码.如果您确实使用lambda本身,仍然可以优化闭包,但需要更积极的过程间优化.

我现在将尝试展示它是如何工作的.我将在我的例子中使用GCC,因为我对它更熟悉.其他编译器应该做类似的事情.

考虑以下代码:

#include <stdio.h>

typedef int (* fnptr_t)(int);
void use_fnptr(fnptr_t fn)
{
    printf("fn=%p, fn(1)=%d\n", fn, fn(1));
}

int main()
{
    auto lam = [] (int x) { return x + 1; };
    use_fnptr((fnptr_t)lam);
}
Run Code Online (Sandbox Code Playgroud)

现在,我编译它并转储中间表示(对于6之前的版本,你应该添加-std=c++11):

g++ test.cc -fdump-tree-ssa
Run Code Online (Sandbox Code Playgroud)

稍微清理和编辑(为简洁起见)转储看起来像这样:

// _ZZ4mainENKUliE_clEi
main()::<lambda(int)> (const struct __lambda0 * const __closure, int x)
{
    return x_1(D) + 1;
}

// _ZZ4mainENUliE_4_FUNEi
static int main()::<lambda(int)>::_FUN(int) (int D.2780)
{
    return main()::<lambda(int)>::operator() (0B, _2(D));
}

// _ZZ4mainENKUliE_cvPFiiEEv
main()::<lambda(int)>::operator int (*)(int)() const (const struct __lambda0 * const this)
{
    return _FUN;
}

int main() ()
{
    struct __lambda0 lam;
    int (*<T5c1>) (int) _3;
    _3 = main()::<lambda(int)>::operator int (*)(int) (&lam);
    use_fnptr (_3);
}
Run Code Online (Sandbox Code Playgroud)

也就是说,lambda有2个成员函数:函数调用操作符和转换操作符以及一个静态成员函数_FUN,它只调用this设置为零的lambda .main调用转换运算符并将结果传递给use_fnptr - 与源代码中编写的完全相同.

我可以写:

extern "C" int _ZZ4mainENKUliE_clEi(void *, int);

int main()
{
    auto lam = [] (int x) { return x + 1; };
    use_fnptr((fnptr_t)lam);
    printf("%d %d %d\n", lam(10), _ZZ4mainENKUliE_clEi(&lam, 11), __lambda0::_FUN(12));
    printf("%p %p\n", &__lambda0::_FUN, (fnptr_t)lam);
   return 0;
}
Run Code Online (Sandbox Code Playgroud)

该计划输出:

fn=0x4005fc, fn(1)=2
11 12 13
0x4005fc 0x4005fc
Run Code Online (Sandbox Code Playgroud)

现在,我认为很明显,编译器应该将lambda(_ZZ4mainENKUliE_clEi)内联到_FUN(_ZZ4mainENUliE_4_FUNEi)中,因为_FUN它是唯一的调用者.内联operator int (*)(int)main(因为这个运算符只返回一个常量).当使用优化(-O)进行编译时,GCC就是这样做的.你可以这样检查:

g++ test.cc -O -fdump-tree-einline
Run Code Online (Sandbox Code Playgroud)

转储文件:

// Considering inline candidate main()::<lambda(int)>.
//   Inlining main()::<lambda(int)> into static int main():<lambda(int)>::_FUN(int).

static int main()::<lambda(int)>::_FUN(int) (int D.2822)
{
    return _2(D) + 1;
}
Run Code Online (Sandbox Code Playgroud)

闭包对象消失了.现在,一个更复杂的情况,当使用lambda本身时(不是函数指针).考虑:

#include <stdio.h>

#define PRINT(x)    printf("%d", (x))
#define PRINT1(x)   PRINT(x); PRINT(x); PRINT(x); PRINT(x);
#define PRINT2(x)   do { PRINT1(x) PRINT1(x) PRINT1(x) PRINT1(x) } while(0)

__attribute__((noinline)) void use_lambda(auto t)
{
    t(1);
}

int main()
{
    auto lam = [] (int x) { PRINT2(x); };
    use_lambda(lam);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

GCC不会内联lambda,因为它相当庞大(这就是我使用printf的方式):

g++ test2.cc -O2 -fdump-ipa-inline -fdump-tree-einline -fdump-tree-esra
Run Code Online (Sandbox Code Playgroud)

早期内联的转储:

Considering inline candidate main()::<lambda(int)>
  will not early inline: void use_lambda(auto:1) [with auto:1 = main()::<lambda(int)>]/16->main()::<lambda(int)>/19, growth 46 exceeds --param early-inlining-insns
Run Code Online (Sandbox Code Playgroud)

但是"早期过程间标量替换聚集体"传递将做我们想要的:

;; Function main()::<lambda(int)> (_ZZ4mainENKUliE_clEi, funcdef_no=14, decl_uid=2815, cgraph_uid=12, symbol_order=12)
IPA param adjustments: 0. base_index: 0 - __closure, base: __closure, remove_param
  1. base_index: 1 - x, base: x, copy_param
Run Code Online (Sandbox Code Playgroud)

不使用第一个参数(即闭包),它将被删除.不幸的是,过程间SRA无法优化远离间接,这是为捕获的值引入的(虽然有些情况下显然有利可图),因此仍有一些增强空间.