使用 + 为 lambda 解决函数指针和 std::function 上的不明确重载

2021-12-23 00:00:00 lambda overloading c++ c++11

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

In the following code, the first call to foo is ambiguous, and therefore fails to compile.

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

The second, with the added + before the lambda, resolves to the function pointer overload.

#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:

The + in the expression +[](){} is the unary + operator. It is defined as follows in [expr.unary.op]/7:

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

The operand of the unary + operator shall have arithmetic, unscoped enumeration, or pointer type and the result is the value of the argument.

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

The lambda is not of arithmetic type etc., but it can be converted:

[expr.prim.lambda]/3

[expr.prim.lambda]/3

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

The type of the lambda-expression [...] is a unique, unnamed non-union class type ― called the closure type ― whose properties are described below.

[expr.prim.lambda]/6

[expr.prim.lambda]/6

没有lambda-capture 的lambda 表达式 的闭包类型有一个publicvirtualexplicit const 转换函数到函数指针 与闭包类型的函数调用操作符具有相同的参数和返回类型.这个转换函数返回的值应该是一个函数的地址,这个函数在调用时与调用闭包类型的函数调用运算符具有相同的效果.

The closure type for a lambda-expression with no lambda-capture has a public non-virtual non-explicit const conversion function to pointer to function having the same parameter and return types as the closure type's function call operator. The value returned by this conversion function shall be the address of a function that, when invoked, has the same effect as invoking the closure type’s function call operator.

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

Therefore, the unary + forces the conversion to the function pointer type, which is for this lambda void (*)(). Therefore, the type of the expression +[](){} is this function pointer type void (*)().

第二个重载 void foo(void (*f)()) 在重载解析的排名中成为精确匹配,因此被明确选择(因为第一个重载不是精确匹配).

The second overload void foo(void (*f)()) becomes an Exact Match in the ranking for overload resolution and is therefore chosen unambiguously (as the first overload is NOT an Exact Match).

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

The lambda [](){} can be converted to std::function<void()> via the non-explicit template ctor of std::function, which takes any type that fulfils the Callable and CopyConstructible requirements.

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

The lambda can also be converted to void (*)() via the conversion function of the closure type (see above).

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

Both are user-defined conversion sequences, and of the same rank. That's why overload resolution fails in the first example due to ambiguity.

根据 Cassio Neri 的说法,由 Daniel Krügler 提供支持,这个一元 + 技巧应该是指定的行为,即你可以依赖它(请参阅评论中的讨论).

According to Cassio Neri, backed up by an argument by Daniel Krügler, this unary + trick should be specified behaviour, i.e. you can rely on it (see discussion in the comments).

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

Still, I'd recommend using an explicit cast to the function pointer type if you want to avoid the ambiguity: you don't need to ask on SO what is does and why it works ;)

相关文章