126
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

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • So basically, g++ *sort-of* translate `std::puts(foo);` into `std::puts(this->foo);` in the generated class (equivalently speaking), whereas clang doesn't do that. – Nawaz Feb 07 '17 at 11:00
  • 1
    Pasting the code from wandbox to [here](http://webcompiler.cloudapp.net/) (they seem to have forgotten the share button) makes it seem like VS2015 (?) agrees with clang saying *warning C4458: declaration of 'foo' hides class member*. – nwp Feb 07 '17 at 11:13
  • 4
    The lambda has a type with a template function call operator, thus the logic would make me say that the parameter should shadow the captured variable as if in `struct Lambda { template void operator()(T foo) const { /* ... */ } private: decltype(outer_foo) foo{outer_foo}; }`. – skypjack Feb 07 '17 at 11:18
  • 2
    @nwp VS is wrong, data members of the lambda are unnamed and thus cannot be shadowed. The standard says "access to a captured entity is transformed to access to the corresponding data member", which leaves us at the square one. – n. m. could be an AI Feb 07 '17 at 11:33
  • 1
    @n.m. Well, those unnamed data members are still accessed by using an identifier. Therefore, the parameter name should at least (let me say) _hide that identifier_. Am I wrong? – skypjack Feb 07 '17 at 11:36
  • 1
    @skypjack this appears to be correct. Ostensibly the lambda body doesn't access the capture, it accesses the original captured entity, which should be shadowed normally according to scope rules. – n. m. could be an AI Feb 07 '17 at 11:41
  • @n.m. Do you think it's worth it to make an answer of my comments? – skypjack Feb 07 '17 at 11:42
  • @skypjack why not, go ahead – n. m. could be an AI Feb 07 '17 at 11:46
  • 10
    I would hope the clang version is correct - it would be breaking new ground if something outside of a function shadows the function parameter, instead of the other way around! – M.M Feb 07 '17 at 13:08
  • @M.M Actually, put aside the standard, common sense is another good reason to say that GCC is wrong in this case. You're right. ;-) – skypjack Feb 07 '17 at 13:33

2 Answers2

66

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.

Columbo
  • 60,038
  • 8
  • 155
  • 203
  • Nothing for me to rip apart here :) A *simple-capture* declares nothing, so the correct result of the name lookup is fairly obvious (BTW, GCC gets it right if you use a *capture-default* instead of explicit capture). *init-capture*s are somewhat trickier. – T.C. Feb 07 '17 at 14:13
  • 1
    @T.C. I agree. I filed a core issue, but apparently this has been discussed already, see the edited answer. – Columbo Feb 08 '17 at 02:04
6

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.

skypjack
  • 49,335
  • 19
  • 95
  • 187
  • As explained above, lookup in this context is never performed as if we were in the transformed closure type. – Columbo Feb 07 '17 at 12:41
  • @Columbo I'm adding a line that I missed even if it was clear from the reasoning, that is that clang is right. The funny part is that I found [expr.prim.lambda]/8 while trying to give an answer, but I've not been able to use it properly as you did. That's why every time it's a pleasure to read your answers. ;-) – skypjack Feb 07 '17 at 12:58