这是我正在尝试运行的代码。
#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([](){});
}
当我尝试doFunc()
使用明确返回布尔值的 lambda 进行调用时,编译器会抱怨:
<source>: In function 'int main()':
<source>:17:22: error: call of overloaded 'doFunc(main()::<lambda()>)' is ambiguous
17 | return doFunc([]() -> bool {return false;});
| ~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
但是,当我传入一个明确返回的 lambda 时void
,它成功打印出 1。为什么它无法识别正确的版本doFunc()
?
我尝试创建一个函数并将其传入而不是 lambda,结果相同。传入 void 函数没问题,但传入返回 bool 的函数会导致相同的错误。
1
最佳答案
3
lambda 不是std::function
,但可以转换为 。
免责声明:
以下信息最初是基于我的猜测。gcc、clang 和 MSVC 的行为似乎支持这一点。但是我无法在标准中指定确认这一点的文章(但由于这不是语言律师的问题,我认为它可能有用)。
后来我添加了一个更新,其中包含来自标准的确认 – 见下文。
第一种情况:
lambda 表达式[]() -> bool {...}
可以转换为 或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()>
。
MSVC(目前在 Godbolt 中已损坏)的行为相同。
更新:
感谢@康桓瑋(在评论中给出 – 这是基于标准的确认:
std::function
调用INVOKE<R>
,其中如果基于[[func.require]] INVOKE<R>
为 void,则为。这意味着满足两个构造函数的约束,从而导致歧义。static_cast<void>(INVOKE(...))
R
[]() -> bool { return false; }
15
-
1@VladfromMoscow 函数不是
std::function
。尝试从普通函数转换为 时发生的问题std::function
与从 lambda 转换为 时发生的问题完全相同std::function
。
– -
2请参阅标准中的部分
func.wrap.func.con
:“F 是参数类型 ArgTypes 的左值可调用… 和返回类型 R”。这是一个相当宽松的规范,但它本质上意味着如果你可以从 F 的返回值中得到 R,那么转换就是有效的。
–
-
3@VladfromMoscow 是的。但这里重要的不是函数的类型。事实上它不是
std::function
(两种变体中的任何一种)并且可以转换为两者。
– -
4@VladfromMoscow 看看。
std::function<bool()>
和std::function<void()>
是不同的类型,正如你所指出的那样。和Foo
也是Bar
。你为什么会认为这个std::function
例子的行为会有所不同?
– -
4
std::function<R(Args ...)>
@VladfromMoscow 它们是不同的类型,但在从某些类型构造时,F
只需要F
能够调用Args...
并能够R
从的返回值中获取F
。如果R
是void
,则可以从任何返回类型中获取它F
–
|
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(*)()
|
的 20.14.16.2.1 部分指出:
template<class F> function(F f);
约束: 对于参数类型和返回类型F
是左值调用(20.14.16.2)。ArgTypes...
R
第 20.14.16.2 节规定:
如果表达式
(被视为未求值操作数(7.2))格式正确(20.14.3),则可调用类型(20.14.2)对于参数类型和返回类型F
都是左值调用。ArgTypes
R
INVOKE<R>(declval<F&>(), declval<ArgTypes>()...)
最后,第 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
调用INVOKE<R>
,其中是INVOKE<R>
基于[ [func.require]]( ) 的。这意味着满足两个构造函数的约束,这就是为什么会产生歧义。static_cast<void>(INVOKE(...))
R
void
[]() -> bool { return false; }
–
|