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

Resolving ambiguous overload on function pointer and std::function for a lambda using + (unary plus)

本文关键字:指针 重载 std 不明确 函数 function lambda 一元 解决 使用      更新时间:2023-10-16

在下面的代码中,对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-expression [...] 的类型是一个唯一的、未命名的非联合类类型——称为闭包类型——其属性如下所述。

[expr.prim.lambda]/6

没有 lambda 捕获lambda 表达式的闭包类型具有publicvirtualexplicit const函数到指向函数的指针,该函数具有与闭包类型的函数调用运算符相同的参数和返回类型。此转换函数返回的值应是函数的地址,该函数在调用时与调用闭包类型的函数调用运算符具有相同的效果。

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

第二个重载void foo(void (*f)())成为重载分辨率排名中的完全匹配,因此被明确选择(因为第一个重载不是完全匹配(。


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

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

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


根据卡西奥·内里(Cassio Neri(的说法,在丹尼尔·克鲁格勒(Daniel Krügler(的论点的支持下,这种一元+技巧应该是特定的行为,即你可以依赖它(见评论中的讨论(。

不过,如果你想避免歧义,我还是建议对函数指针类型使用显式强制转换:你不需要问SO它的作用和为什么它;)