使用+解决lambda的函数指针和std :: function上的模棱两可的重载


93

在以下代码中,对的第一次调用foo是不明确的,因此无法编译。

第二个+在lambda之前添加,解析为函数指针重载。

#include <functional>

void foo(std::function<void()> f) { f(); }
void foo(void (*f)()) { f(); }

int main ()
{
    foo(  [](){} ); // ambiguous
    foo( +[](){} ); // not ambiguous (calls the function pointer overload)
}

+这里的符号是什么?

Answers:


98

+表达式中的+[](){}是一元+运算符。在[expr.unary.op] / 7中定义如下:

一元运算+符的操作数应具有算术,无作用域枚举或指针类型,并且结果是自变量的值。

lambda不是算术类型等,但是可以将其转换为:

[expr.prim.lambda] / 3

lambda表达式的类型是唯一的,未命名的非工会类类型(称为闭包类型),其属性如下所述。

[expr.prim.lambda] / 6

用于闭合型λ-表达λ-捕获具有publicvirtualexplicit const转换功能指针函数具有相同的参数和返回类型为闭合类型的函数调用操作。该转换函数返回的值应是一个函数的地址,该地址在被调用时与调用闭包类型的函数调用操作符具有相同的作用。

因此,一元+强制转换为该lambda的函数指针类型void (*)()。因此,表达式的类型+[](){}就是该函数指针类型void (*)()

第二个过载void foo(void (*f)())在过载解决方案的排名中成为“精确匹配”,因此可以明确选择(因为第一个过载不是“精确匹配”)。


[](){}可以std::function<void()>通过的非显式模板ctor 将lambda 转换为,该模板std::function可以采用满足CallableCopyConstructible要求的任何类型。

lambda也可以void (*)()通过闭包类型的转换函数转换为(见上文)。

两者都是用户定义的转换序列,并且具有相同的等级。这就是第一个示例中由于歧义而导致重载解析失败的原因。


根据卡西欧·内里(Cassio Neri)的支持,丹尼尔·克鲁格勒(DanielKrügler)的论点支持了这一一元+技巧,应该将其指定为行为,即您可以依靠它(请参阅评论中的讨论)。

尽管如此,如果您想避免歧义,我还是建议对函数指针类型使用显式转换:您无需询问SO的作用和作用;)


3
@Fred AFAIK成员函数指针不能转换为非成员函数指针,更不用说函数左值了。您可以将成员函数通过绑定std::bindstd::function对象,该对象可以类似于函数左值调用。
dyp

2
@DyP:我相信我们可以依靠棘手的问题。确实,假设实现将添加operator +()到无状态闭包类型。假定此运算符返回闭包类型转换成的函数的指针以外的其他值。然后,这将改变违反5.1.2 / 3的程序的可观察行为。请让我知道您是否同意这种推理。
卡西欧内里

2
@CassioNeri是的,这就是我不确定的地方。我同意添加时可观察到的行为可能会改变operator +,但这operator +与没有开始的情况相比。但是没有指定闭包类型不能有operator +重载。“实现可以定义与以下描述不同的闭包类型,但前提是这样做不会改变程序的可观察行为(除非通过...)”,但IMO 添加操作符不会将闭包类型更改为不同于如下所述。
dyp

3
@DyP:operator +()完全没有标准描述的情况。该标准允许实现执行与指定操作不同的操作。例如,添加operator +()。但是,如果程序可以观察到这种差异,那么这是非法的。我曾经在comp.lang.c ++。moderated中问过,闭包类型是否可以为其添加typedef,result_type以及是否typedefs需要其他类型使它们适应(例如by std::not1)。有人告诉我不能,因为这是可以观察到的。我将尝试查找链接。
卡西欧内里

6
VS15给您这个有趣的错误:test.cpp(543):错误C2593:'operator +'是不明确的t \ test.cpp(543):注意:可以是内置的C ++ operator +(void(__cdecl *)(void ))'t \ test.cpp(543):注意:或'内置C ++运算符+(void(__stdcall *)(void))'t \ test.cpp(543):注意:或'内置C ++运算符+ (void(__fastcall *)(void))'t \ test.cpp(543):note:或'内置C ++运算符+(void(__vectorcall *)(void))'t \ test.cpp(543):note :尝试匹配参数列表'(wmain :: <lambda_d983319760d11be517b3d48b95b3fe58>)test.cpp(543):错误C2088:'+':对于类是非法的
Ed Lambert
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.