考虑一下简单的程序:
int i = 0;
int& j = i;
auto lambda = [=]{
std::cout << &j << std::endl; //odr-use j
};
Run Code Online (Sandbox Code Playgroud)
根据[expr.prim.lambda],闭包成员变量j
应该具有以下类型int
:
如果实体被隐式捕获并且捕获默认值是或者如果使用不是形式&标识符或标识符初始值设定项的捕获显式捕获实体,则通过副本捕获实体.对于由副本捕获的每个实体,在闭包类型中声明未命名的非静态数据成员.这些成员的声明顺序未指定.如果实体不是对对象的引用,则这种数据成员的类型是对应的捕获实体的类型,否则是引用的类型.
=
所以我什么印刷是一些地址int
无关的外范围i
或j
.这一切都很好.但是,当我投入时decltype
:
auto lambda = [j] {
std::cout << &j << std::endl;
static_assert(std::is_same<decltype(j), int>::value, "!"); // error: !
};
Run Code Online (Sandbox Code Playgroud)
由于decltype(j)
评估为,因此无法编译int&
.为什么?j
在该范围内应该引用数据成员,如果不是?
作为一个相关的后续,如果lambda捕获是一个init-capture with [j=j]{...}
,那么 clang将报告decltype(j)
as int
和not int&
.为什么不同?
名称查找在lambda表达式中的工作方式有点特殊:引用由副本捕获的实体的id表达式从对捕获实体的访问转换为对闭包类型的存储数据成员的访问 - 但仅限于这些访问构成odr用途.请注意,由于隐式捕获,如果没有使用odr,则可能没有此类数据成员.
decltype
不构成使用odr,因此它将始终引用捕获的实体(原始),而不是数据成员(副本).
C++ 11 [expr.prim.lamba] p17
每个id-expression是由副本捕获的实体的odr使用,它被转换为对闭包类型的相应未命名数据成员的访问.
此外,p18甚至在一个例子中显示了这种奇怪的效果:
每次出现的
decltype((x))
wherex
都是一个可能带括号的 id-expression,它命名一个自动存储持续时间的实体,被视为x
转换为对闭包类型的相应数据成员的访问,如果x
是odr使用的话,该成员将被声明.表示实体.[ 例如:Run Code Online (Sandbox Code Playgroud)void f3() { float x, &r = x; [=] { // x and r are not captured (appearance in a decltype operand is not an odr-use) decltype(x) y1; // y1 has type float decltype((x)) y2 = y1; // y2 has type float const& because this lambda // is not mutable and x is an lvalue decltype(r) r1 = y1; // r1 has type float& (transformation not considered) decltype((r)) r2 = y2; // r2 has type float const& }; }
- 结束例子 ]
由于C++ 14 [expr.prim.lambda] p15 ,C++ 14 init- capture也被认为是通过拷贝捕获的.
如果实体被隐式捕获并且 捕获默认值是或者如果使用不是表单标识符或标识符初始化程序的捕获显式捕获实体,则通过副本捕获实体.
=
&
&
然而,正如TC指出的那样,它们并没有捕获它们已被初始化的实体,而是捕获了一个"虚拟变量",它也用于类型推导[expr.prim.lambda] p11
一个INIT-捕获的行为就好像它声明和明确地捕获形式"的变量
auto
初始化捕获;
",其陈述区域是λ-表达的化合物的语句[...]
类型推导改变了这个变量的类型,例如char const[N]
- > char const*
,原始实体甚至可能没有类型,例如[i = {1,2,3}]{}
.
因此,lambda中的id-expression 指的是这个虚拟变量,而它的类型不是.j
[j=j]{ decltype(j) x; }
int
int&