这是我尝试运行的代码。
#include <functional>
int doFunc(std::function<void()> someFunc)
{
someFunc();
return 1;
}
int doFunc(std::function<bool()> someFunc)
{
someFunc();
return 2;
}
int main()
{
return doFunc([]() -> bool {return false;});
//return doFunc([](){});
}
当我尝试使用明确返回 bool 的 lambda 调用
doFunc()
时,编译器会抱怨:
<source>: In function 'int main()':
<source>:17:22: error: call of overloaded 'doFunc(main()::<lambda()>)' is ambiguous
17 | return doFunc([]() -> bool {return false;});
| ~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
但是,当我传入一个明确返回
void
的lambda时,它成功打印出1。为什么它无法识别doFunc()
的正确版本?
我尝试创建一个函数并传递它而不是 lambda,我得到了相同的结果。传入 void 函数没问题,但传入返回 bool 的函数会导致相同的错误。
lambda 不是
std::function
,但可以转换为 1。
免责声明:
以下信息最初基于我的有根据的猜测。 gcc、clang 和 MSVC 的行为似乎支持它。然而,我无法在标准中指定确认它的文章(但由于这不是语言律师问题,我认为它可能有用)。
后来我添加了一个更新并得到了标准的确认 - 见下文。
第一种情况:
像
[]() -> bool {...}
这样的 lambda 可以转换为 std::function<void()>
或 std::function<bool()>
,因为返回值可以被丢弃。doFunc([]() -> bool {return false;})
是不明确。
第二种情况:
另一方面,像
[](){}
这样的 lambda 无法转换为 std::function<bool()>
(只能转换为 std::function<void()>
),因为它缺少可能需要的返回值,因此调用是 不模糊的 。
为了解决第一种情况的歧义,您可以将 lambda 分配给
std::function
变量:
std::function f = []() -> bool {return false; };
return doFunc(f);
编译器将被迫推断
std::function
的确切类型,并选择最接近 lambda 的类型,即:std::function<bool()>
。
现场演示(gcc + clang)
MSVC(目前在 Godbolt 中已被破坏)的行为相同。
更新:
由@康桓玮提供(在评论中给出)-这是基于标准的确认:
std::function
调用 INVOKE<R>
,其中 INVOKE<R>
是 static_cast<void>(INVOKE(...))
,如果 R
基于 [[func.require]](eel.is/c++draft/func.require) 为 void。这意味着 []() -> bool { return false; }
满足两个构造函数的约束,导致混乱。
C++20(草案)标准的 20.14.16.2.1 部分说:
第 20.14.16.2 节说:
template<class F> function(F f);
约束:F
对于参数类型ArgTypes...
和返回类型R
是左值可调用的 (20.14.16.2)。
可调用类型 (20.14.2)最后,第 20.14.3 节说:
F
对于参数类型ArgTypes
和返回类型R
是左值可调用的,如果表达式INVOKE<R>(declval<F&>(), declval<ArgTypes>()...)
,被视为未评估的操作数 (7.2),是 格式良好 (20.14.3).
定义因此,考虑
INVOKE(f, t1, t2, ..., tN)
如下:[...]
定义
f(t1, t2, ..., tN)
在所有其他情况下。
INVOKE<R>(f, t1, t2, ..., tN)
为static_cast<void>(INVOKE(f, t1, t2, ..., tN))
如果R
是cvvoid
,否则INVOKE(f, t1, t2, ..., tN)
隐式转换为R
。
F
为
decltype([]() -> bool {return false;})
,则
INVOKE<void>(declval<F&>())
和
INVOKE<bool>(declval<F&>())
都是良构的。因此,
F
可以转换为
std::function<void()>
或
std::function<bool()>
,因此两个重载都是同样可行的候选者,并且函数调用是不明确的。
std::function
不是函数指针。不要求您传递给它的函数(以任何形式)具有您定义的签名。事实上,这就是它的工作:它有代码来调整参数类型和返回类型。像这样:
bool f();
std::function<void()> func(f); // okay
void (*ptr)() = f; // error: can't convert bool (*)() to void (*)()
在内部,调用 func()
将调用
f
并忽略返回值。然后你就可以这样做(这就是
std::function
的创建目的:
double g();
func = g;
func(); // calls g and ignores the return value
同样,
void f(double);
std::function<void(int)> func(f); // okay
void (*ptr)(int) = f; // error: can't convert void (*)(double) to void (*)(int)
在内部,调用 func(3)
会将
3
转换为
double
并调用
f(3.0)
。然后你可以这样做:
void g(int);
func = g;
func(3);
在内部,调用 func(3)
会将
3
传递给
g
。因此,为了使其更接近您的示例:
void func(std::function<void()>);
bool f();
func(f); // okay; std::function<void()> can hold bool (*)()
同样,
void func(std::function<bool()>);
bool f();
func(f); // okay; std::function<bool()> can hold bool (*)()
因此,当您同时拥有 func
的两个版本时,调用是不明确的:
void func(std::function<void()>);
void func(std::function<bool()>);
bool f();
func(f); // error: both std::function<void()> and std::function<bool()>
// can hold a function pointer of type bool(*)()