C++ Lambda无需捕获即可使用的实体疑问及标准解析
Lambda表达式无需捕获规则的两个疑问解析
背景规则
Lambda表达式无需捕获即可使用变量的情况:
- 变量为非局部变量,或具有static/thread local存储期(此类变量无法被捕获);或
- 变量是由常量表达式初始化的引用。
Lambda表达式无需捕获即可读取变量值的情况:
- 变量为const非volatile整数或枚举类型,且由常量表达式初始化;或
- 变量是constexpr且无mutable成员。
已验证第1、3条规则的代码如下:
验证第1条规则的代码
int x = 17; // 非局部变量 static int y = 17; // 静态存储期变量(验证正确) int main () { auto g = [x, y] // 编译错误:x、y属于不可捕获的变量类型 { (void)(x + y); }; // 改为空捕获列表即可正常编译:auto g = []{ (void)(x + y); }; }
验证第3条规则的代码
struct C{}; int main () { int nc{}; // 非const变量,必须捕获 constexpr int ce{3}; int const cce{ce}; // const且由常量表达式初始化,无需捕获 int const cn{nc}; // const但由非常量表达式初始化,必须捕获 int const volatile cv{}; // const但带volatile,必须捕获 C const c{}; auto lam = [c, nc, cn, cv]{ (void)(nc + cce + cn + cv); (void)c; }; lam(); }
疑问1:“由常量表达式初始化的引用”具体含义
你给出的示例constexpr int x{3}; int const& y = x;其实完全符合这条规则——只要引用是在lambda作用域外定义,且初始化它的表达式是常量表达式,lambda就无需捕获该引用即可直接使用。
正确的使用示例:
constexpr int x = 3; const int& ref_x = x; // 由常量表达式初始化的引用 int main() { auto lam = []{ (void)ref_x; // 无需捕获,编译正常 }; lam(); }
根据C++标准,这类引用的初始化依赖常量表达式,绑定的要么是静态存储期对象,要么是编译期可求值的常量表达式结果。在lambda中使用时,相当于直接访问被引用的对象,而该对象本身要么属于无需捕获的类型(静态存储期),要么是编译期值,因此无需捕获引用本身。
疑问2:带mutable成员的constexpr对象为何无需捕获?
你提供的测试代码:
constexpr struct M { mutable int x; } m{3}; int main () { auto lam = []{ (void)m; // 未捕获却编译正常 }; lam(); }
关键在于规则的适用场景:规则针对的是“读取变量值”的行为,且你的代码并未odr-use(单定义规则使用)对象m。根据C++标准,(void)m这类表达式只是丢弃对象的值,没有访问对象的任何成员、地址或修改状态,不属于odr-use的范畴。此时即使对象包含mutable成员,也无需捕获。
如果尝试访问或修改mutable成员,就必须捕获对象:
constexpr struct M { mutable int x; } m{3}; int main () { auto lam = []{ m.x = 5; // 编译错误:必须捕获m才能修改其mutable成员 (void)m.x; // 同样错误:读取mutable成员属于odr-use,需捕获 }; lam(); }
只有当constexpr对象没有mutable成员时,对其任何合法的读取操作(包括成员访问)都无需捕获。如果存在mutable成员,只有不触发odr-use的操作(如单纯的(void)m)才可以免捕获。
内容的提问来源于stack exchange,提问作者Enlico
相关产品推荐
相关产品推荐

