Lambda capture reference by copy and decltype

2019-02-17 18:47发布

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?

标签: c++ c++11 lambda
1条回答
冷血范
2楼-- · 2019-02-17 19:36

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&.

查看更多
登录 后发表回答