C++ Lambda中非ODR使用的变量为何引用外部变量?如何获取捕获变量类型?
Lambda捕获与decltype相关技术问题
示例代码
void foo(int &par) { auto l = [par]() { decltype(par) x; }; }
这段代码无法编译:decltype(par)中的par引用的是foo的参数而非Lambda的捕获变量,因此decltype(par)的结果是引用类型,而引用类型变量必须初始化,最终触发编译错误。
相关C++14标准规则
Lambda表达式复合语句内,每个作为被拷贝捕获实体的ODR-use的id表达式,都会被转换为对闭包类型对应无名数据成员的访问。[注:非ODR-use的id表达式引用的是原实体,而非闭包类型的成员。此外,此类id表达式不会触发实体的隐式捕获。——结束注]
所有形如
decltype((x))的表达式(其中x是可能带括号的、命名自动存储期实体的id表达式),都会被视为已将x转换为对闭包类型对应数据成员的访问,就如同x是对所指实体的ODR-use一样。
根据这些规则,Lambda内部的变量引用可能指向捕获的变量,也可能指向外围函数的原变量,由此引出以下两个问题:
问题1:为何如此设计?为何不始终使用捕获变量?这种额外的复杂性能带来什么好处?
这种设计是为了平衡语义一致性、效率与安全:
- 对于非ODR-use的场景(比如仅用
decltype获取类型、或作为常量表达式的一部分),直接引用原实体能避免不必要的捕获开销,同时保持与Lambda外部代码的语义一致——毕竟此时并没有真正使用变量的存储,只是查询类型等属性。 - ODR-use的场景必须访问捕获的成员:Lambda可能在原实体生命周期结束后执行,此时原实体已不存在,必须依赖闭包内保存的拷贝来保证安全。
- 额外的复杂性换来的是灵活性:既保证Lambda延迟执行时的安全性,又在不需要实际访问存储时避免冗余操作,同时维持类型查询等操作的直观性。
问题2:如何在Lambda内部获取捕获变量的类型?
有几种可靠的实现方式:
- 方法1:使用
decltype((par))
按照标准规则,带双重括号的decltype((par))会被视为对捕获成员的访问,能直接得到捕获变量的类型:void foo(int &par) { auto l = [par]() { decltype((par)) x = par; // 此时decltype((par))为int,而非int& }; } - 方法2:结合
std::decay_t推导
利用std::decay_t去除可能的引用、cv限定,配合decltype获取捕获变量的本质类型:#include <type_traits> void foo(int &par) { auto l = [par]() { using CaptureType = std::decay_t<decltype(par)>; CaptureType x; // x的类型为int }; } - 方法3:通过绑定捕获成员推导
先将par绑定到捕获成员的引用,再通过decltype获取类型:void foo(int &par) { auto l = [par]() { auto& capture_ref = par; // capture_ref绑定到捕获的成员 using CaptureType = decltype(capture_ref); CaptureType x = par; }; }
内容的提问来源于stack exchange,提问作者geza
相关产品推荐
相关产品推荐

