使用+(一元加号)解决lambda的函数指针和std::function上的多义性重载

2022-02-21 00:00:00 lambda overloading c++ c++11

在下面的代码中,对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)
}

+符号在这里做什么?


解决方案

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

一元+运算符的操作数应为算术、无作用域枚举或指针类型,结果为参数的值。

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

[expr.prim.lambda]/3

lambda表达式[.]的类型是一种唯一的、未命名的非联合类类型,称为闭包类型,其属性如下所述。

[expr.prim.lambda]/6

lambda-expression没有lambda-capture的闭包类型有一个publicvirtualconst转换函数,该函数指向与闭包类型的函数调用运算符具有相同参数和返回类型的函数指针。此转换函数返回的值应为调用时与调用闭包类型的函数调用运算符具有相同效果的函数的地址。

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

第二个重载void foo(void (*f)())在重载解析排名中变为完全匹配,因此被毫不含糊地选择(因为第一个重载不是完全匹配)。


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

也可以通过闭包类型的转换函数将lambda转换为void (*)()(如上所述)。

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


根据Cassio Neri的说法,并得到Daniel Krügler的支持,这个一元的+技巧应该是指定的行为,也就是说,您可以依赖它(参见评论中的讨论)。

不过,如果您想避免歧义,我建议您使用显式转换到函数指针类型:您不需要问它做了什么,为什么它会工作;)

相关文章