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