为什么对以 std::function 作为参数的重载函数的调用被认为是不明确的?

问题描述 投票:0回答:3

这是我尝试运行的代码。

#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 的函数会导致相同的错误。

c++ std-function
3个回答
8
投票

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; }
满足两个构造函数的约束,导致混乱。


3
投票

C++20(草案)标准的 20.14.16.2.1 部分说:

template<class F> function(F f);



约束: F
 对于参数类型 
ArgTypes...
 和返回类型 
R
 是左值可调用的 (20.14.16.2)。

第 20.14.16.2 节说:

可调用类型 (20.14.2)

F

 对于参数类型 
ArgTypes
 和返回类型 
R
 是左值可调用的,如果表达式
INVOKE<R>(declval<F&>(), declval<ArgTypes>()...)
,被视为未评估的操作数 (7.2),是
格式良好 (20.14.3).

最后,第 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 void
,否则 
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()>
,因此两个重载都是同样可行的候选者,并且函数调用是不明确的。


1
投票

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(*)()
    
© www.soinside.com 2019 - 2024. All rights reserved.