使用lambda函数签名进行函数重载

12

请看下面的例子

void foo(const std::function<int()>& f) {
    std::cout << f() << std::endl;
}

void foo(const std::function<int(int x)>& f) {
std::cout << f(5) << std::endl;
}

int main() {
    foo([](){return 3;});

    foo([](int x){return x;});
}

这段代码不能编译,因为调用foo被认为是模糊的。据我所知,这是因为 lambda 函数不是预先定义好的std::function类型,需要将其强制转换为该类型,并且有一个std::function构造函数可以接受任意参数。

也许有人可以解释一下,为什么会创建一个带有任意参数的隐式构造函数。然而,我的实际问题是是否有一种解决方法,允许使用lambda函数的函数签名重载函数foo。我已经尝试了函数指针,但由于捕获的lambda函数无法转换为普通函数指针,它并没有起作用。

非常感谢任何帮助。


7
这可能是MSVC中的一个错误。在Clang/GCC中可以正常工作。 - user3920237
3
哦,微软,你真有趣。 - user123
1
@remyabel和Mohammad,我认为微软先生应该得到道歉。这不是一个好的行为方式! - user932887
2
抱歉,微软先生。 ;_; - user123
1
好的,谢谢大家。我用GCC 4.8测试了一下,实际上它可以工作。所以我可能需要升级我的GCC。 - Haatschii
显示剩余3条评论
2个回答

16

根据C++11标准,您的编译器是正确的。在C++14中,添加了一条规则,即除非参数的类型实际上可以与std::function的参数类型进行调用,否则构造函数模板不得参与重载决议。因此,在C++14中,此代码应该能够编译,但在C++11中则不能。可以认为这是C++11中的疏忽。

现在,您可以通过显式转换来解决这个问题:

foo(std::function<int()>([](){return 3;}));

感谢澄清。根据评论,我尝试使用GCC 4.8(和-std=c++11),它起作用了。有点有趣的是,较新版本的GCC因此未严格使用正确的c++11。 - Haatschii
1
@Haatschii 这是C++11标准中的一个缺陷;编译器/库开发人员通常会追溯地应用修复程序。(此外,在C++11中它已经是UB了,所以具有C++14行为是允许的。) - T.C.

8

http://coliru.stacked-crooked.com/a/26bd4c7e9b88bbd0

使用模板是std::function的替代方案。模板避免了与std::function相关的内存分配开销。模板类型推导机制将推断传递的lambda的正确类型,因此调用站点转换消失了。但是,仍然需要消除无参和有参情况的重载歧义。
可以使用尾返回类型的技巧来实现这一点,这类似于enable_if。
template<typename Callable>
auto baz(Callable c) 
    -> decltype(c(5), void())
{
    std::cout << c(5) << std::endl;
}

当模板参数 Callable 可以使用 5 作为参数调用时,baz 的上述重载将成为有效的重载候选。

您可以在此基础上添加更高级的机制,使其更加通用(例如将 args 可变包展开到 callable 中),但我想展示基本机制的工作原理。


你的coliru示例中似乎缺少了#include <functional> - kyb
请告诉我 decltype(c(5), void()) 是如何工作的或者在哪里可以阅读到相关信息?据我所知,c(5) 只是为了表明我们期望 Callable c 接受一个 int 类型的参数,然后操作符和真实返回类型,更准确地说是“构造”一个 void 类型的值以用作 decltype 的参数。因此,诀窍在于限制 Callable 可以使用某些参数进行调用,然后返回 void。对吗? - kyb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接