[&i] ( ) { std::cout << i; }
// is equivalent to
struct anonymous
{
int &m_i;
anonymous(int &i) : m_i(i) {}
inline auto operator()() const
{
std::cout << m_i;
}
};
首先我知道,一个如上的 lambda 表达式,其实相当于生成了一个如上匿名 struct 的实例 a ,运行 lambda 表达式时,其实就相当于执行 a().
int main()
{
int v1 = 42;
auto f = [v1]() {return ++v1; };//值捕获
v1 = 0;
auto j = f(); //j 为 43
}
但是如上的代码却无法通过编译,除非你加上 mutable 。所以我是不是可以认为,如上的 lambda 表达式,是不是实际生成了如下的 struct ?
struct anonymous
{
int m_i;
anonymous(int i) : m_i(i) {}
inline auto operator()() const//如果你加上 mutable ,这里的 const 才会去掉
{
m_i++;
}
};
另外,c++ lambda 表达式是不是都可以认为 它的等价情况,就是生成了一个 重载了 operator()的匿名结构体的实例?如果不是的话,请帮忙举一个反例。
1
elfive 2022-03-06 11:05:54 +08:00 via iPhone
[vl]这是按值捕获,要用引用捕获[&vl]
|
2
codehz 2022-03-06 11:10:12 +08:00 via Android
Lambda 确实就是匿名类型的实例化而已(没捕获的时候还带一个转换成函数指针的运算符重载)
https://en.cppreference.com/w/cpp/language/lambda |
3
missdeer 2022-03-06 11:10:52 +08:00
这么理解没错,可以上 Compiler Explorer 验证一下,比如带 mutable 就是没 const: https://godbolt.org/z/87EaPznM8
不带 mutable 就是有 const: https://godbolt.org/z/Y51KdT74j 不过虽然现在的 gcc 和 msvc 确实都把 lambda 用仿函数实现,但这只是实现的方式,标准应该没说一定要这么做 |
4
amiwrong123 OP @elfive #1
是的,改成这样就可以编译通过。但我想知道,按值捕获的 lambda 表达式到底 实际上生成了什么样子的匿名结构体实例(然后才会造成,无法修改 按值捕获的变量)。 |
5
victorbian 2022-03-06 11:15:53 +08:00 1
你想的没错,mutable 就是去掉 const ,而默认加 const 是为了让仿函数每次执行结果一致
https://cppinsights.io/s/fc8369d5 https://stackoverflow.com/a/5503690/8263383 |
6
dangyuluo 2022-03-06 12:54:40 +08:00
Lambda 生成的类 operator()() 默认是 const 的,Google 搜索一下就有,实在是搜不到再发帖。
看这里是怎么生成的 https://cppinsights.io/s/8efa4ddf |
7
dangyuluo 2022-03-06 12:55:55 +08:00 1
文档里写: https://en.cppreference.com/w/cpp/language/lambda
Unless the keyword mutable was used in the lambda-expression, the function-call operator or operator template is const-qualified and the objects that were captured by copy are non-modifiable from inside this operator() |
8
dcsuibian 2022-03-06 15:17:46 +08:00
我的理解,这是实现机制上的问题。Java 里也有一样的要求来着。
lambda 里的 v1 并不是外面的 v1 。例如,原来的 v1 地址是 0x12345678 ,而 lambda 里的 v1 的地址是 0x23456789 ,它是把原来 0x12345678 里存的东西放到了 0x23456789 里面。所以实际上这俩不是一个东西。所以 v1 并没有跳脱它的作用域。 那为什么要默认给他加个 const ?我以 Java 打比方。 ```java public void test() { int v1=5; Runnable runnable=()->{ v1+=2; }; runnable.run(); System.out.println(v1); } ``` 这个程序里面,创建了一个 lambda 并立即执行,我们会很自然地认为输出的 v1 是 7 ,但从实现的角度来说,由于 lambda 表达式里的 v1 并不是外面的那个 v1 ,仍然是 5 。 语言开发者为了避免出现这个奇怪的现象就禁止了在 lambda 里面修改 v1 ,所以我这个程序里 runnable 里的 v1+=2 这个部分会报错。 |
9
amiwrong123 OP @elfive #1
@codehz #2 @missdeer #3 @victorbian #5 @dangyuluo #6 @dcsuibian #8 各位大佬,问个问题。 https://cppinsights.io/s/69a816df 就是如上这个代码,为什么我看 main 函数里生成的 local class 里,有这么定义的 Member templates: ``` template<class ... type_parameter_0_0> inline auto operator()(type_parameter_0_0... param) const { print(param...); } ``` 但实际上,local class 不能有 Member templates ( https://zh.cppreference.com/w/cpp/language/member_template 这里有说,你把 右边的代码放进 vs 也是编译不能通过)。 所以,为什么实际生成的代码 违反了 invalid declaration of member template in local class ? 另外,之所以还需要 void print() {}这个函数定义,是不是因为:最后一次调用 void print(const First& first, Rest &&... args)时,第二个参数 args 实际上已经是 void ,所以接着又会调用到 void print() {}里面?(不知道我这么解释对不对,所以不对,请帮忙用正确的术语描述一下😂) |
10
amiwrong123 OP 另外, 楼上的代码是在 cppinsights 的 C++14 上执行的哈
|
11
codehz 2022-03-08 22:08:51 +08:00 via Android
Cpp insight 也就图一乐,为了便于理解才这样生成)
看这个去理解标准那无异于刻舟求剑( |
12
victorbian 2022-03-09 02:28:33 +08:00
@amiwrong123 C++14 允许 lambda 表达式( local class 的一种)带有 member templates ,参见 [cppreference]( https://en.cppreference.com/w/cpp/language/class#:~:text=Local%20classes%20other%20than%20closure%20types%20(since%20C%2B%2B14)%20cannot%20have%20member%20templates),估计正因为如此 C++14 中 lambda 表达式才开始支持 auto 形参。
关于 `void print(){}`,cppinsights 里已经很清楚了,最后一次调用 `void print<double>(const double & first)` 会调用 `print();` |