这是我正在尝试运行的代码。

#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

  • 2
    std::function调用INVOKE<R>,其中INVOKE<R>基于[ [func.require]]( ) 的。这意味着满足两个构造函数的约束,这就是为什么会产生歧义。static_cast<void>(INVOKE(...))Rvoid[]() -> bool { return false; }


    – 


最佳答案
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。如果Rvoid,则可以从任何返回类型中获取它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)将转换3double并调用f(3.0)

然后你可以这样做:

void g(int);
func = g;
func(3);

在内部,调用func(3)将传递3g

因此,为了使其更接近你的例子:

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都是左值调用。ArgTypesRINVOKE<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
cv voidINVOKE(f, t1, t2, ..., tN)隐式转换为R

因此,考虑Fdecltype([]() -> bool {return false;}),则INVOKE<void>(declval<F&>())INVOKE<bool>(declval<F&>())都是规范的。

因此,F可以转换为std::function<void()>std::function<bool()>,因此两个重载都是同样可行的候选者,并且函数调用是不明确的。