在堆栈溢出问题中,为什么要重新定义C ++ 11中不允许的lambda,为什么?给出了一个无法编译的小程序:
int main() {
auto test = []{};
test = []{};
}
问题被回答,一切似乎都很好。然后来到约翰尼斯·绍布(Johannes Schaub),并做了一个有趣的观察:
如果在第一个lambda前面放一个
+
,它就会神奇地开始工作。所以我很好奇:为什么下面的工作?
int main() {
auto test = +[]{}; // Note the unary operator + before the lambda
test = []{};
}
它可以在GCC 4.7+和Clang 3.2+上正常编译。代码符合标准吗?
#1 楼
是的,该代码符合标准。+
触发了lambda到普通旧函数指针的转换。将发生以下情况:
编译器将看到第一个lambda(
[]{}
)并根据第5.1.2节生成一个闭包对象。由于lambda是不可捕获的lambda,因此适用以下规则:5.1.2 Lambda表达式[expr.prim.lambda]
6没有lambda的lambda表达式的闭包类型-capture具有公共非虚拟非显式const转换函数,该函数指向与闭包类型的函数调用运算符具有相同参数和返回类型的函数的指针。该转换函数返回的值应该是一个函数的地址,该函数的地址在被调用时与调用闭包类型的函数调用运算符具有相同的作用。
这很重要,因为一元运算符
+
具有一个一组内置重载,特别是以下一组:13.6内置运算符[over.built]
8对于每种
T
,都存在形式为的候选运算符
T* operator+(T*);
这样,就很清楚了:发生什么事:将运算符
+
应用于闭包对象时,重载的内置候选集包含对任何指针的转换和闭包类型恰好包含一个候选:转换为lambda的函数指针。因此,将
test
中auto test = +[]{};
的类型推导为void(*)()
。现在,第二行很容易:对于第二个lambda / closure对象,对函数指针的分配触发与第一行相同的转换。即使第二个lambda具有不同的闭包类型,结果函数指针当然也是兼容的并且可以分配。评论
迷人。一元+指针的意义是什么?我了解数字类型的存在以确保一元性-的完整性。但是一元-用指针没有意义。
–塔德乌斯·科佩克(Tadeusz Kopec)
2013年9月19日上午8:14
如果要强制衰减数组或函数,并且要强制提升小整数类型或无作用域枚举,则此方法很有用。
– Johannes Schaub-小人
2013年9月19日上午9:17
@TadeuszKopec…对于所有类型,它都会删除左值。您可以使用它来传递值,而不是引用两个函数都重载的函数,例如通过完美的转发。
–马铃薯
2013年9月19日上午10:04
@Potatoswatter:“并且对于所有类型,它都会消除左值性。”并非为所有类型都定义+ lvalue,对于某些类型的+ lvalue来说,它只是一个重载的函数调用,因此可以产生任何不相关类型的任何值类别,在某些情况下,它不会产生相同类型。使用它通过值而不是引用来传递将是一种奇怪的用法。更好的方法是使用static_cast
–安德鲁·托马佐斯(Andrew Tomazos)
2013年9月19日上午11:04
@ user1131467我只是说,如果您不是专门使用内置的operator +,则应该定义一个函数val,该函数可以有效地执行您提到的static_cast,而不是坚持使用前缀+表示法。
–马铃薯
2013年9月19日15:13
评论
有趣的是,对于捕获lambda而言,它将不起作用。@MatthieuM。因为捕获lambda不会衰减到函数指针! ;)
接下来是另一个+资料来源。在GCC上尝试:struct foo {static const int n = 100; }; int main(){返回std :: max(0,+ foo :: n); }。如果删除+,它将无法链接,这是符合标准的行为。 VS2010链接起来没有问题(即使没有+也是如此)。
至少相关:使用+
解决lambda的函数指针和std :: function上的模棱两可的重载
让我们添加更多的魔术:自动测试= * [] {}; (注意x在这里仍然是一个函数指针,由于衰减,我认为),然后..自动测试= + * [] {};。当然,您可以无限重复此操作:自动测试= * + * + * + [] {};。我最喜欢的是:自动测试= + * ??(:>()<%??>;