在Stack Overflow问题中,c++ 11中不允许重新定义lambda,为什么?,给出了一个不能编译的小程序:

int main() {
    auto test = []{};
    test = []{};
}

问题得到了回答,一切似乎都很好。然后约翰内斯·绍布做了一个有趣的观察:

如果你在第一个前面加一个+,它就神奇地开始工作了。

所以我很好奇:为什么下面的方法有效?

int main() {
    auto test = +[]{}; // Note the unary operator + before the lambda
    test = []{};
}

它可以在GCC 4.7+和Clang 3.2+下编译。代码符合标准吗?


是的,代码符合标准。+触发lambda到普通旧函数指针的转换。

结果是这样的:

编译器看到第一个lambda([]{})并根据§5.1.2生成一个闭包对象。由于lambda是一个非捕获lambda,因此应用如下:

5.1.2 Lambda表达式[exp .prim. Lambda] 没有lambda-capture的lambda-表达式的闭包类型具有一个公共的非虚拟的非显式const转换函数,该函数指向具有与闭包类型的函数调用操作符相同的形参和返回类型的函数。此转换函数返回的值应该是函数的地址,该函数在调用时与调用闭包类型的函数调用操作符具有相同的效果。

这很重要,因为一元操作符+有一组内置重载,特别是这个:

13.6内置操作符[over.built] 对于每一种类型T,都存在这样形式的候选算子函数 T *运算符+ (T *);

这样,发生的事情就很清楚了:当operator +应用于闭包对象时,重载内置候选集包含一个转换到任意指针的候选:转换到lambda的函数指针。

auto test = +[]{}中的测试类型;因此被推导为void(*)()。现在第二行很简单:对于第二个lambda/closure对象,对函数指针的赋值触发与第一行相同的转换。尽管第二个lambda具有不同的闭包类型,但生成的函数指针当然是兼容的,并且可以被赋值。