为什么我不能在C++11中创建一个相同类型的lambda向量?

98

我试图创建一个 lambda 函数的向量,但失败了:

auto ignore = [&]() { return 10; };  //1
std::vector<decltype(ignore)> v;     //2
v.push_back([&]() { return 100; });  //3

到第2行为止,编译通过。但第3行出现了编译错误:

error: no matching function for call to 'std::vector<main()::<lambda()>>::push_back(main()::<lambda()>)'

我不想要一个函数指针的向量或者函数对象的向量。但是,将真正的lambda表达式封装在函数对象中,这对我是可行的。这种情况可能吗?


24
你说你不想要一个函数指针向量或者函数对象向量,但这是你所要求的。一个lambda表达式 就是 一个函数对象。 - Nicol Bolas
6个回答

147

每个 lambda 表达式都有不同的类型—即使它们具有相同的签名。如果您想要执行此类操作,必须使用运行时封装容器,例如std::function

例如:

std::vector<std::function<int()>> functors;
functors.push_back([&] { return 100; });
functors.push_back([&] { return  10; });

53
管理一百人的开发团队对我来说更像是一场噩梦 :) - Jeremy Friesner
11
不要忘记没有捕获参数的lambda表达式([]-风格)可以退化为函数指针。因此,他可以存储相同类型的函数指针数组。注意,VC10尚未实现该功能。 - Nicol Bolas
顺便问一下,在这些例子中,不应该使用无捕获的lambda吗?或者说这是必要的吗?顺便说一句,VC11好像支持将无捕获的lambda转换为函数指针。虽然我没有测试过。 - Klaim
通常我发现我更喜欢通过引用来捕获所有内容,除非我有特定的想法。这样做可以使得回头修改变得更加容易,因为这是我期望的行为。 - Puppy
2
能否创建一个存储不同类型函数的向量?即,除了限制为 std::function<int(),我能使用不同的函数原型吗? - manatttta
2
@manatttta 有什么意义呢?容器的存在是为了存储相同类型的对象,以便一起组织和操作它们。你可以问“我能创建一个同时存储std::functionstd::stringvector吗?”答案仍然是不行,因为这不是预期的用法。你可以使用“variant”风格的类来执行足够的类型擦除,将不同的东西放入容器中,并包括一个方法让用户确定“真实”的类型,从而选择如何处理(例如如何调用)每个元素...但再次问一下,为什么要这样做呢?有任何真正的理由吗? - underscore_d

41
所有的lambda表达式都有不同的类型,即使它们在字符层面上是相同的。你正在将不同类型的lambda(因为它是另一个表达式)推入向量中,这显然行不通。一种解决方法是改用std::function<int()>类型的向量。 示例代码
auto ignore = [&]() { return 10; };
std::vector<std::function<int()>> v;
v.push_back(ignore);
v.push_back([&]() { return 100; });

另外,当你没有捕获任何内容时,使用[&]不是一个好主意。


16
无需对不接受参数的 lambda 表达式使用 () - Puppy

19

虽然其他人所说的是相关的,但仍然有可能声明并使用lambda的向量,尽管它并不是非常有用:

auto lambda = [] { return 10; };
std::vector<decltype(lambda)> vec;
vec.push_back(lambda);

所以,只要是 lambda 的复制/移动,您可以在那里存储任意数量的 lambda!


假设在循环中以不同参数发生了阻力反弹,这项功能可能实际上是有用的。预计是为了懒惰评估的目的。 - MaHuJa
7
不,你不需要将参数放入向量中,只需将函数对象放入其中。因此,这将是一个包含所有副本的相同lambda的向量。 - hariseldon78

18
如果您的 lambda 表达式是无状态的,即 [](...){...},那么 C++11 允许它退化为函数指针。理论上,一个符合 C++11 标准的编译器应该可以编译以下代码:
auto ignore = []() { return 10; };  //1 note misssing & in []!
std::vector<int (*)()> v;     //2
v.push_back([]() { return 100; });  //3

4
记录一下,auto ignore = *[] { return 10; }; 会使得 ignore 成为一个 int(*)() 类型。 - Luc Danton
1
@Luc,哦,太恶心了!他们什么时候添加的? - MSN
3
由于允许将函数指针作为参数的转换函数被规定为非"explicit(显式)",因此解引用 lambda 表达式是有效的,会得到从转换中产生的指针。然后使用 auto 会将该引用衰减回指针。(使用 auto&auto&& 可以保留该引用。) - Luc Danton
啊...解引用结果指针。这很有道理。漏掉的 () 是故意的还是偶然的? - MSN
有意思的是,Lambda表达式等效于(但比其短两个字符)。 - Luc Danton
它可以工作。我使用了具有与std :: function相同性能的std :: vector <int(*)(int,int)> 进行测试。因此似乎没有理由使生活更加困难。 - iaomw

9
你可以使用一个生成 lambda 函数(更新后,根据 Nawaz 的建议进行了修复):
#include <vector>
#include <iostream>

int main() {
    auto lambda_gen = [] (int i) {return [i](int x){ return i*x;};} ;

    using my_lambda = decltype(lambda_gen(1));

    std::vector<my_lambda> vec;

    for(int i = 0; i < 10; i++) vec.push_back(lambda_gen(i));

    int i = 0;

    for (auto& lambda : vec){
        std::cout << lambda(i) << std::endl;
        i++;
    }
}

但我认为你现在基本上已经创建了自己的类。否则,如果lambda函数具有完全不同的捕获/参数等内容,则可能需要使用元组。


将其包装在像lambda_gen这样的函数中是个好主意,它本身可以成为一个lambda。然而,auto a = lambda_gen(1);做了一个不必要的调用,如果我们写成decltype(lambda_gen(1)),就可以避免这种情况。 - Nawaz
这是否仍会产生额外的调用呢?另外一个小问题是问题说明了C ++11,所以我认为需要向函数添加尾返回类型。 - antediluvian
任何在 decltype 中的内容都是未求值的,因此调用实际上并没有被执行。这也适用于 sizeof。即使您添加尾随返回类型,这段代码在C++11中也无法工作! - Nawaz

4
每个lambda都是不同的类型。你必须使用std::tuple而不是std::vector

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