Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lambda capture reference by copy and decltype

Tags:

c++

c++11

lambda

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?

like image 756
Barry Avatar asked Oct 01 '15 18:10

Barry


People also ask

Does a lambda capture copy?

By default, variables are captured by const value . This means when the lambda is created, the lambda captures a constant copy of the outer scope variable, which means that the lambda is not allowed to modify them.

Does lambda capture reference by value?

Lambdas always capture objects, and they can do so by value or by reference.

How do you capture variables in lambda?

Capture clause A lambda can introduce new variables in its body (in C++14), and it can also access, or capture, variables from the surrounding scope. A lambda begins with the capture clause. It specifies which variables are captured, and whether the capture is by value or by reference.

How do you pass a lambda function in C++?

Permalink. All the alternatives to passing a lambda by value actually capture a lambda's address, be it by const l-value reference, by non-const l-value reference, by universal reference, or by pointer.


1 Answers

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

like image 195
dyp Avatar answered Sep 19 '22 03:09

dyp