在C++中向lambda表达式传递参数

20

我似乎在C++的lambda机制中错过了一些要点。以下是代码:

std::vector<int> vec (5);

int init = 0;
std::generate(begin(vec), end(vec), [init]() mutable { return ++init; });

for (auto item : vec) {
    std::cout << item << " ";
}
std::cout << std::endl << init << std::endl;
如果没有mutable,编译会失败,因为我正在lambda中更改init
现在,我的理解是lambda针对每个向量项使用新的副本来调用,该副本为0。 所以,必须每次返回1。 但是,这段代码的输出是:
1 2 3 4 5
0
看起来generate仅在其执行的开始时通过复制捕获init,但为什么?它是否应该像这样工作?

这正是它应该工作的方式。 - kmdreko
1
如果你使用了一个 for 循环,并在循环体中执行类似于 vec[i] = ([init] () mutable {return ++init;})(); 的操作,那么你可能会得到你所描述的结果,因为这样会在每次迭代时创建一个 lambda 函数。 - Bakuriu
@Bakuriu。这就是我困惑的地方——我认为for循环和generate(begin, end, lambda)应该做同样的事情。 - IgorStack
5个回答

32
现在,据我所知,lambda会针对每个向量的项调用一次,同时使用一个新的初始值0的副本。
这是不正确的。Lambda只是创建类并为其提供operator()的另一种方式。Lambda中的[]部分描述了成员变量及其是否被引用或值捕获。Lambda中的()部分是operator()的参数列表,{}部分是该函数的主体。mutable部分告诉编译器将operator()作为非const函数生成,默认情况下它是const函数。
因此,
[init]() mutable { return ++init; }
成为
struct compiler_generated_name
{
    int init; // we captured by value

    auto operator()() // since we used mutable this is non const
    {
        return ++init;
    }
};

这里我使用了结构体以缩短输入的长度,但是lambda被指定为类类型,因此可以使用 class

这意味着 init 与上一次迭代中的 init 相同,因为你只会捕获一次。需要记住这一点,因为这很重要。

auto generate_lambda()
{
    int foo = 0;
    return [&foo](){ return ++foo; };
}

当函数返回并使用它时,将留下指向 foo 的悬空引用,并且使用它是未定义行为。


1
这是我看过的最好/最简单的实现lambda表达式的解释。谢谢! - Jim Fell

10

lambda是编译器生成的结构体,相当于:

struct lambda
{
    int init = 0; // captured value

    auto operator()() // non-const, due to `mutable`
    {
        return ++init;
    }
};
因此,init 仅在 lambda 内部被捕获并复制一次 - 调用多次 lambda 不会再次捕获 init

5

您正在复制并查看init的初始值 —— 根据您的期望,可能想要做的是通过引用来捕获init.....

std::vector<int> vec (5);

int init = 0;
std::generate(begin(vec), end(vec), [&init]() mutable { return ++init; });

for (auto item : vec) {
    std::cout << item << " ";
}
std::cout << std::endl << init << std::endl;

我已经多次使用了 [&param] 并且知道它的工作原理。这只是一个示例代码,我在阅读关于可变应用于 lambda 后运行了它。 - IgorStack
这如何解释为什么会生成一个越来越长的值列表,捕获引用又如何修复它? - NathanOliver
@NathanOliver:这是在考验我吗?)) - IgorStack
@IgorStack 不是。我在问Soren,因为我不明白他想回答什么问题。 - NathanOliver
我在解释为什么最后一行的输出是0 -- 不知怎么想到那是问题所在了。 - Soren
显示剩余3条评论

3
你的错误在于这里:“现在,我理解lambda是针对每个向量项调用一个新的init副本”,(我的斜体)。不,正如你所看到的,lambda是完全独立的,因此无法了解向量代码。对item的初始化发生在每次lambda形式本身被评估时(与每次调用结果值的时间相反); 在这里,这意味着每次调用函数generate时:只有一次。

1
int x = ([](int j)->int {return j; }, 3);

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