什么是const lambda?

19
#include <iostream>
int foo(int i)
{
    const auto a = [&i](){ i = 7; };
    a();
    return i;
}
int main()
{
    std::cout << foo(42) << std::endl;
    return 0;
}

这个代码可以编译( g++ -std=c++11 -Wall -Wextra -Wpedantic main.cpp ), 并打印出7。这让我很惊讶,因为当我把a声明为一个常量对象时,我本来期望i会被引用为const int&。显然它不是,为什么?


5
你的代码中哪部分使用了const引用来捕获j?我不理解问题所在。你编写了一个函数,接收i作为参数,忽略它的值,将其设为7,然后求平方并返回结果(49)... 这恰好就是发生的事情!对于j,你只是暂时将49存储在一个命名变量中然后返回它。你能澄清一下你的意图吗? - Lightness Races in Orbit
2
现在你根本没有一个叫做j的变量。你对修改后的代码所做的只是将i设置为7,返回49,然后丢弃这个49的值。 - Lightness Races in Orbit
@Vorac:抱歉,如果不知道你是如何得出这个结论的,我无法纠正你的误解,除了说“编译器是正确的”(或者解释lambda函数的每一个细节,希望能找到你理解错误的部分……但我不会这样做!) - Lightness Races in Orbit
@LightnessRacesinOrbit 我尝试通过编辑来澄清问题。我还能提供什么其他的背景信息,以便问题更加清晰明了? - Vorac
1
我来晚了,但确实这是 C++ Insights 的一个不错的用例:https://cppinsights.io/s/fe317135 - andreee
显示剩余7条评论
6个回答

20

Lambdas就像非lambda函数一样,只是它们的实现细节被隐藏起来了。因此,我们可以通过使用一个非lambda的函数对象来更容易地解释:

#include <iostream>
int foo(int i)
{
    struct F {
      int &i;
      int operator()() const { i = 7; return i * i; }
    };
    const F a {i};
    a();
    return i;
}
int main()
{
    std::cout << foo(42) << std::endl;
    return 0;
}

F有一个int &引用成员iconst F不能修改其实例数据,但对i的修改不是对其实例数据的修改。对其实例数据的修改将重新绑定i到另一个对象(无论如何都不允许)。


15
[&i](){ i = 7; return i * i; }

主要相当于

class Lambda
{
public:
    Lambda(int& arg_i) : i(arg_i) {}

    auto operator() () const { i = 7; return i * i;}
private:
    int& i;
};

那么你就有:

const Lambda a(i);
a();

而且 const Lambda 不会将其成员提升为 const int& i;,而是 int& const i;,它等同于 int& i;


8

当你捕获i时,它被捕获为它所属的类型。

因此,在内部,它有一个int&。在闭包变量声明之前加上一个const对于lambda没有任何影响。

你有两个解决方案:

const int i = 5;
auto b = [&i]() { i++; }; //error on i++

这样就可以捕获一个const int&

如果出于某种原因无法更改i,在c++14中可以这样做。

int i = 5;
auto b = [i = static_cast<const int&>(i)]() { i++; }; //error on i++

这将把 int& 转换为 const int& 并在 lambda 表达式中存储。虽然这样做更冗长,但您可以看到。


如果他不想改变 i,那最好通过拷贝来传递它。 - krzaq
@krzaq,是的,但这对于您不想复制的较大对象非常有用。 对于int这种情况,这种方法似乎并不需要。 - Hayt

2
在你提供的代码中:
int foo(int i)
{
    const auto a = [&i](){ i = 7; return i * i; };
    a();
    return i;
}

在您初始化常量lambda函数后,您没有进行分配操作。因此,在这种情况下, const 的意义不是很大。


我并没有,但是你的回答对我来说似乎没有意义。 - krzaq
@krzaq 在我的回答中已经说明了:OP最初初始化了常量lambda。然后他没有给那个lambda分配一个不同的值,所以const修饰符在这里并不起到很大的作用。你对此有什么不理解的吗? - amanuel2
我也不知道。@Vorac:没什么……这意味着你不能修改a或其成员,但在这里你也没有尝试这样做。你期望它会自动将内部int&更改为const int&吗?- Lightness Races in Orbit 9分钟前。你的答案解决了第一个问题,而我的困惑在于第二个问题。我编辑了问题,试图澄清这一点。 - Vorac
@Vorac,你不明白什么,请说明一下。 - amanuel2
1
我给它点了踩,因为它没有提供任何有价值的信息。 - Lightness Races in Orbit
@LightnessRacesinOrbit 好吧,我猜你认为不回答问题没有任何价值。 - amanuel2

2

如果我理解正确,问题是为什么可以改变 i 的值,即使 aconst,并且可能包含对 i 的引用作为成员。

答案是因为你可以在任何对象上这样做的原因 - 分配给 i 不会修改 lambda 实例,而是修改它所引用的对象。

示例:

class A
{
public:
    A(int& y) : x(y) {} 
    void foo(int a) const { x = a; } // But it's const?!
private:
    int& x;
};

int main()
{
    int e = 0;
    const A a(e);
    a.foo(99);
    std::cout << e << std::endl;
}

这段代码编译通过,输出 "99",因为 foo 没有修改 a 的成员,而是修改了 e
(这有点令人困惑,但思考哪些对象被修改并忽略它们的名称会有所帮助。) const 的“常量但实际上不是”特性是引起混淆和烦恼的非常普遍的源头。
指针的行为方式正是如此,其中这更明显不是错误:
class A
{
public:
    A(int* y) : x(y) {} 
    void foo(int a) const { *x = a; } // Doesn't modify x, only *x (which isn't const).
private:
    int* x;
};

1
你声明为const的并不是你匿名函数或lambda表达式及其参数的上下文,而仅仅是指向该lambda表达式的引用:const auto a
因此,你无法更改lambda表达式引用a的值,因为它是常量,但是在lambda表达式的上下文中可以更改通过引用传递的参数&i的值。

2
a 不是一个引用。 - Lightness Races in Orbit
我没有深入到语言细节层面... 我认为给出一个变量分配lambda表达式地址的角色的抽象指示已经足够了,也就是:一个对lambda表达式或匿名函数的“引用”... - Ciro Corvino
2
这不是一个引用。 - Lightness Races in Orbit

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