Lambda捕获引用通过copy和decltype [英] Lambda capture reference by copy and decltype

查看:207
本文介绍了Lambda捕获引用通过copy和decltype的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑简单的程序:

  int i = 0; 
int& j = i;

auto lambda = [=] {
std :: cout< & j<< std :: endl; // odr-use j
};

根据[expr.prim.lambda],closure成员变量 j 应该有 int


如果其被隐式捕获并且捕获默认 = ,或者如果它被明确捕获,则通过复制捕获的捕获,其不是形式&标识符或&标识符初始化器。对于通过复制捕获的每个实体,在闭包类型中声明未命名的非静态数据成员。这些成员的声明顺序未指定。 此类数据成员的类型是相应捕获的实体的类型,如果实体不是对对象或引用类型

的引用。


所以我打印的是一些 int 的地址与外部范围无关 i j 。这一切都很好。但是,当我引入 decltype

  auto lambda = [j ] {
std :: cout<< & j<< std :: endl;
static_assert(std :: is_same< decltype(j),int> :: value,!); //错误:!
};

无法编译,因为 decltype(j)计算为 int& 。为什么? j 在该范围应该指向数据成员,应该不是?



作为一个相关的后续, lambda捕获代替一个 init捕获 [j = j] {...} ,然后 而不是 int& 。为什么有区别?

解决方案

名称查找在lambda表达式中工作的方式有点奇怪: id-expressions which参考由拷贝捕获的实体从捕获的实体的访问变换为对封闭类型的存储的数据成员的访问,但是只有当这些访问构成 odr- / em> 。注意,由于隐式捕获,如果没有odr使用,可能没有这样的数据成员。



decltype 不构成odr使用,因此它将始终引用捕获的实体(原始),而不是数据成员(副本)。



C ++ 11 [expr.prim.lamba] p17


每个id表达式都是 odr使用
副本捕获的实体被转换为对闭包类型的相应未命名数据
成员的访问。



每次出现 decltype((x) )其中 x 是可能带括号的
id-expression
被视为如果 x 被转换为访问闭包类型的相应
数据成员,如果 x
是表示实体的odr使用。 [示例:

  void f3(){
float x,& r = X;
[=] {// x和r未捕获(在decltype操作数中的外观不是odr使用)
decltype(x)y1; // y1有类型float
decltype((x))y2 = y1; // y2有float const&因为这个lambda
//不是可变的,x是一个左值
decltype(r)r1 = y1; // r1 has type float& (转换不考虑)
decltype((r))r2 = y2; // r2 has type float const&
};
}

- 结束示例

b $ b






C ++ 14 init-captures 自从C ++ 14捕获实体

如果其被隐式捕获并且
capture-default = ,或者如果它被明确地捕获,则通过复制
捕获不是& 标识符& 形式的

/ 2756719 / tc> TC 已经指出,它们不捕获它们已经初始化的实体,而是一个也用于类型推导的虚拟变量[expr.prim.lambda] p11


一个 init-capture 的行为就像它声明并显式地捕获一个
形式的变量 auto init-capture ; ,其声明区域是
< ...]


类型推导改变这个变量的类型,例如 char const [N] - > char const * ,原始实体甚至可能没有类型。 [i = {1,2,3}] {}



[j = j] {decltype(j)x; 中定义 j } 是指这个虚拟变量,其类型为 int ,而不是 int& 。 / p>

Consider the simple program:

int i = 0;
int& j = i;

auto lambda = [=]{
    std::cout << &j << std::endl; //odr-use j
};

According to [expr.prim.lambda], the closure member variable j should have type int:

An entity is captured by copy if it is implicitly captured and the capture-default is = or if it is explicitly captured with a capture that is not of the form & identifier or & identifier initializer. For each entity captured by copy, an unnamed non-static data member is declared in the closure type. The declaration order of these members is unspecified. The type of such a data member is the type of the corresponding captured entity if the entity is not a reference to an object, or the referenced type otherwise.

So what I'm printing is the address of some int unrelated to the outer-scope i or j. This is all well and good. However, when I throw in decltype:

auto lambda = [j] {
    std::cout << &j << std::endl;
    static_assert(std::is_same<decltype(j), int>::value, "!"); // error: !
};

That fails to compile because decltype(j) evaluates as int&. Why? j in that scope should refer to the data member, should it not?

As a related followup, if the lambda capture were instead an init-capture with [j=j]{...}, then clang would report decltype(j) as int and not int&. Why the difference?

解决方案

The way name lookup works inside lambda-expressions is a bit peculiar: id-expressions which refer to entities captured by copy are transformed from accesses to the captured entities to accesses to the stored data members of the closure type -- but only if these accesses constitute odr-uses. Note that due to implicit capture, if there's no odr-use, there is possibly no such data member.

decltype does not constitute an odr-use, hence it will always refer to the captured entity (the original), not the data member (the copy).

C++11 [expr.prim.lamba]p17

Every id-expression that is an odr-use of an entity captured by copy is transformed into an access to the corresponding unnamed data member of the closure type.

and furthermore, p18 even displays this weird effect in an example:

Every occurrence of decltype((x)) where x is a possibly parenthesized id-expression that names an entity of automatic storage duration is treated as if x were transformed into an access to a corresponding data member of the closure type that would have been declared if x were an odr-use of the denoted entity. [ Example:

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&
    };
}

end example ]


The C++14 init-captures are also considered capture by copy, since C++14 [expr.prim.lambda]p15

An entity is captured by copy if it is implicitly captured and the capture-default is = or if it is explicitly captured with a capture that is not of the form & identifier or & identifier initializer.

However, as T.C. has pointed out, they do not capture the entity they've been initialized with, but rather a "dummy variable" which is also used for type deduction [expr.prim.lambda]p11

An init-capture behaves as if it declares and explicitly captures a variable of the form "auto init-capture ;" whose declarative region is the lambda-expression’s compound-statement [...]

The type deduction alters the type of this variable, e.g. char const[N] -> char const*, and the original entity might not even have a type, e.g. [i = {1,2,3}]{}.

Therefore, the id-expression j in the lambda [j=j]{ decltype(j) x; } refers to this dummy variable and its type is int, not int&.

这篇关于Lambda捕获引用通过copy和decltype的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆