Lambda Capture and Parameter with Same Name - Who Shadows the Other? (Clang VS Gcc)

Lambda capture and parameter with same name - who shadows the other? (clang vs gcc)

Update: as promised by the Core chair in the bottom quote, the code is now ill-formed:

If an identifier in a simple-capture appears as the declarator-id of a parameter of the lambda-declarator's parameter-declaration-clause, the program is ill-formed.


There were a few issues concerning name lookup in lambdas a while ago. They were resolved by N2927:

The new wording no longer relies on lookup to remap uses of captured entities. It more clearly
denies the interpretations that a lambda's compound-statement is processed in two passes or that any names in that compound-statement might resolve to a member of the closure type.

Lookup is always done in the context of the lambda-expression, never "after" the transformation to a closure type's member function body. See [expr.prim.lambda]/8:

The lambda-expression's compound-statement yields the function-body ([dcl.fct.def]) of the function call operator, but for purposes of name lookup, […], the compound-statement is considered in the context of the lambda-expression. [ Example:

struct S1 {
int x, y;
int operator()(int);
void f() {
[=]()->int {
return operator()(this->x+y); // equivalent to: S1::operator()(this->x+(*this).y)
// and this has type S1*
};
}
};

end example ]

(The example also makes clear that lookup does not somehow consider the generated capture member of the closure type.)

The name foo is not (re)declared in the capture; it is declared in the block enclosing the lambda expression. The parameter foo is declared in a block that is nested in that outer block (see [basic.scope.block]/2, which also explicitly mentions lambda parameters). The order of lookup is clearly from inner to outer blocks. Hence the parameter should be selected, that is, Clang is right.

If you were to make the capture an init-capture, i.e. foo = "" instead of foo, the answer would not be clear. This is because the capture now actually induces a declaration whose "block" is not given. I messaged the core chair on this, who replied

This is issue 2211 (a new issues list will appear on the open-std.org site shortly, unfortunately with just placeholders for a number of issues, of which this is one; I'm working hard to fill in those gaps before the Kona meeting at the end of the month). CWG discussed this during our January teleconference, and the direction is to make the program ill-formed if a capture name is also a parameter name.

Why visual c++ (latest) and gcc 12.1 accepted hiding this init capture for lambda, while clang 14.0.0 not? (c++20)

I think that clang is correct in rejecting snippet 1 and accepting snippet 2 because in the first case the non-static data member is named y while in the second case the non-static data member is unnamed.

Case 1

Here we consider snippet 1:

int main() {
int x = 100;
auto lamb_var = [y = x](){ //the data member is "named" y
int y = 10; //error because we're defining y for second time
return y + 1;
};

assert (lamb_var() == 11);

return 0;
}

Now, from expr.prim.lambda#capture-6:

An init-capture without ellipsis 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, except that:

(emphasis mine)

This seems to indicate that the non-static data member has a name which in your given example is y. Now, by writing int y = 10; we're providing a redefinition of the same named variable y in the same declarative region and hence the error.


Note that we will get the same error(as expected due to the reason explained above), if we replace [y=x] with [x=x] and int y =10; with int x = 10; as shown below:

int main() {
int x = 100;
auto lamb_var = [x = x](){ //data member "named" x
int x = 10; //this will give same error
return x + 1;
};

assert (lamb_var() == 11);

return 0;
}

Case 2

Here we consider the snippet 2:

int main() {
int x = 100;
auto lamb_var = [x](){ //data member is unnamed
int x = 10; //ok because we're defining an int variable with "name" x for the first time in this region
return x + 1;
};

assert (lamb_var() == 11);

return 0;
}

Here from expr.prim.lambda#capture-10:

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

(emphasis mine)

In this case, the non-static data member is unnamed and so writing int x = 10; is not a redefinition error because we're definining a variable named x for the first time in this region.

Variable shadowed warning in lambda (when not captured)

You are correct, lambda a doesn't shadow main::a because main::a isn't captured in the lambda, thus is not on scope there.

However I think about what the purpose of the shadow warning is: to avoid programmer confusion. If a long body of code if the programmer sees the outer declaration, but doesn't see the inner declaration he or she may incorrectly assume that a use of the inner variable refers to the outer variable. And this possible confusion still applies here, even if the variable doesn't tehnically shadow the outer one.

I don't know if this is the intention of the warning or if it is a bug. Or even if that is a compelling enough reason to have a warning even a differently worded one. Shadow warning are problematic anyways. You can find a lot of discussions and bug reports about shadow warning that even if tehnically correct are considered harmful.

What are the differences between these 4 lambda expressions?

In this context, they all produce the same results. However, there are logical differences between them.

  • [](double value) { return MyFunction(value, 1.0, 2.0); }

    This is a lambda which takes a single parameter of type value and passes this into MyFunction. Its return type is deduced from the return statement to be that of MyFunction, which is double.

  • [](double value) -> double { return MyFunction(value, 1.0, 2.0); }

    This is the same lambda as before, but this time its return type is explicitly specified to be double. It's the same in this case, but it would be different from the first one if the return type of MyFunction was something else. In that case, the first one would return what MyFunction returns, while this one would still return double.

  • [value](double value) { return MyFunction(value, 1.0, 2.0); }

    This one depends on the standard version used. In C++11 and 14, this one captures main's local variable value. However, that capture is hidden by the lambda's parameter value, so it's effectively useless. It would be different if the lambda had been declared as e.g. [value](double v) { return MyFunction(value, 1.0, 2.0); }. This would have passed on the captured value, and not its parameter.

    In C++17 and above, this was changed and it's actually ill-formed (compilation error). Naming a lambda parameter the same as something you capture is no longer allowed.

    Since the change was a defect report (CWG 2211), it applies retroactively and so it's legal for compilers to reject such code even in earlier C++ versions.

  • [value](double value) -> double { return MyFunction(value, 1.0, 2.0); }

    Same as lambda number 3, with explicit return type specification (the difference between 3 and 4 is exactly the same as between 1 and 2).

Is there a compilation flag to detect duplicate lamda parameter-lambda member?

Upgrade your compilers.

Based on https://godbolt.org/z/h3Y7dW1dP that warning is missing for gcc 8.5 (with -std=c++17) and clang 7.1.0.

You should upgrade to gcc 9.1 and clang 8.0.0 or later. (Preferably a lot later.)

No compiler diagnostic when identifier in a simple-capture appears as the declarator-id of a parameter

That wording was added to C++17 to resolve CWG Defect 2211. It didn't exist in C++14, and it would seem that Clang and GCC haven't caught up to this change up to the versions you are checking.

It's worth noting that the GCC trunk does indeed diagnose that program as ill-formed under C++17.



Related Topics



Leave a reply



Submit