C++嵌套Lambda拷贝捕获父Lambda变量的编译差异疑问
为什么两种Lambda捕获写法在g++和clang中表现不同?
这个问题确实有点绕,咱们先把场景理清楚,再一步步拆解原因:
首先看你遇到的第一个编译错误场景,代码是这样的:
#include <functional> void test() { int a = 5; std::function<void()> f = [a](){ [a]()mutable{ // 看起来是按拷贝捕获外层lambda的'a' a = 13; // g++报错:assignment of read-only variable 'a' }(); }; }
g++ 8.3.0会报错,但把内层lambda的捕获改成[a{a}]后,代码就能正常编译:
#include <functional> void test() { int a = 5; std::function<void()> f = [a](){ [a{a}]()mutable{ // 显式拷贝初始化 a = 13; // 无编译错误 }(); }; }
而且clang++ 7.0.1能直接编译通过第一段代码,这到底是怎么回事?
核心原因:外层Lambda的const属性带来的编译器处理差异
咱们先明确几个关键知识点:
- 外层的
[a](){ ... }没有加mutable,所以它的调用运算符operator()是const成员函数。这意味着在这个lambda的内部,所有成员变量(包括它捕获的a)都会被视为const的,不能直接修改。 - 内层lambda
[a]()mutable试图捕获的是外层lambda里的成员变量a,这个变量的原始类型是int(因为外层lambda是按拷贝捕获外部的int a),只是在const成员函数里被限定为不可修改。
根据C++标准的规定:当lambda捕获外层lambda的成员变量时,捕获的是该变量的原始类型(也就是int),而不是当前上下文的const限定。内层lambda加了mutable,意味着它自己的成员变量是可以修改的——所以理论上第一段代码是完全合法的。
为什么g++报错,clang却能通过?
这其实是g++ 8.3.0的一个编译器bug:它错误地将内层lambda捕获的a判定为const int类型,即使内层lambda加了mutable,也认为这个变量是只读的,所以抛出了错误。而clang的处理是符合标准的,它正确识别出捕获的是原始的int类型,允许mutable的lambda修改自己的拷贝。
为什么[a{a}]能绕过这个bug?
当你写成[a{a}]时,相当于显式地用外层lambda中a的值(虽然在const上下文,但值是可以读取的)来初始化内层lambda的成员变量a。这种显式的拷贝初始化让g++正确识别出内层lambda的a是int类型,而非const,所以mutable修饰后就可以正常修改了。
总结
- 第一种写法
[a]在C标准中是合法的,g 8.3.0的报错属于编译器实现问题; - clang的处理符合标准,所以能正常编译;
- 第二种显式拷贝的写法是一个有效的 workaround,能绕过g++的这个bug。
内容的提问来源于stack exchange,提问作者igagis
相关产品推荐
相关产品推荐

