Lambda capture and parameter with same name - who

2019-01-10 19:50发布

问题:

auto foo = "You're using g++!";
auto compiler_detector = [foo](auto foo) { std::puts(foo); };
compiler_detector("You're using clang++!");
  • clang++ 3.6.0 and newer print out "You're using clang++!" and warn about the capture foo being unused.

  • g++ 4.9.0 and newer print out "You're using g++!" and warn about the parameter foo being unused.

What compiler is more accurately following the C++ Standard here?

wandbox example

回答1:

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.



回答2:

I'm trying to pack together a few comments to the question to give you a meaningful answer.
First of all, note that:

  • Non-static data members are declared for the lambda for each copy-captured variable
  • In the specific case, the lambda has a closure type which has a public inline template function call operator accepting a parameter named foo

Therefore the logic would make me say at a first glance that the parameter should shadow the captured variable as if in:

struct Lambda {
    template<typename T> void operator()(T foo) const { /* ... */ }
    private: decltype(outer_foo) foo{outer_foo};
};

Anyway, @n.m. correctly noted that the non-static data members declared for copy-captured variables are actually unnamed. That being said, the unnamed data member is still accessed by means of an identifier (that is foo). Therefore, the parameter name of the function call operator should still (let me say) shadow that identifier.
As correctly pointed out by @n.m. in the comments to the question:

the original captured entity [...] should be shadowed normally according to scope rules

Because of that, I'd say that clang is right.