9

My linter claims that the lambda in the following code implicitly captures this (and wants to have that capture specified explicitly). Is that correct?

template <class X>
struct OtherTemplate
{
  static X bar()
  {
    return 1;
  }
};

template <class Y>
struct Test
{
  void foo()
  {
    auto lambda = [&]() {
      // return (Y)1;
      // return std::min<Y>(1, 2);
      return OtherTemplate<Y>::bar();
    };
    (void)lambda;
  }
};

// Explicit instantiation
template struct Test<int>;

https://godbolt.org/z/cvV59J

There is no complaint if the lambda has no default capture, or if any of the commented lines are used instead. Removing the "templatedness" from OtherTemplate also produces no diagnostic.

It seems obvious to me that this is not used in the lambda. However, the following two questions seem to have observed similar behavior:

The C++11 standard (to pick one) indicates to me that capturing this would require ODR-using this ([expr.prim.lambda]/11). I don't see how that happens here, but I've been surprised before by the technicalities of template name lookups.


Edit: Due to discussions in the comments, I would like to emphasize the following note from the standard, in particular the can:

Implicit odr-uses of this can result in implicit capture.

That should make clear that a capture default does not unilaterally capture this.


So before I file a bug with the linter, I wanted to make sure:
Does the above lambda implicitly capture this?

I am most interested in C++17, but I would appreciate answers also covering the other standard versions.

Max Langhof
  • 23,383
  • 5
  • 39
  • 72
  • @ThomasSablik could you expand? Like the OP I also can't see how `this` is ODR-used within the lambda, unless the implicit instantiation of `OtherTemplate` from the scope of a member function for some reason implies ODR-using `this`. My guess would be that this is a false positive from the linter, but I'm interested in hearing from some of the language lawyer experts on this topic. – dfrib May 14 '20 at 10:29
  • @Peter I don't see how you conclude that from the quoted text. That paragraph only states "you can't do `[&, &myVar]` or `[=, myVar]`, only `[&, myVar]` and `[=, &myVar]`". It takes absolutely no stance on what is captured with default captures. Take note that it says "of the form", not "`this` is captured". – Max Langhof May 14 '20 at 10:41
  • The following note from [\[expr.prim.lambda.capture\]/2](https://timsong-cpp.github.io/cppwp/n4659/expr.prim.lambda#capture-2) of the C++17 standard draft is kind of interesting though [**emphasis** mine]: _"Note: The form `[&,this]` is **redundant** but accepted for compatibility with ISO C++ 2014.  — end note"_. This would imply that (as of C++17) explicitly capturing `this` as a _simple-capture_ is redundant _no matter the use of `this` in the body of the lambda_? Which in turn would apply `this` is always implicitly captured for a _capture-default_-only capture list of `[&]`? – dfrib May 14 '20 at 10:46
  • [\[expr.prim.lambda.capture\]/2](https://eel.is/c++draft/expr.prim.lambda.capture#2) has some useful notes/example code. – G.M. May 14 '20 at 10:47
  • `this->static_method()` is valid. Your linter should turn the call that way... – Jarod42 May 14 '20 at 10:50
  • @Jarod42 I've been thinking along those lines, but there is no static method in `this`. The static method is in a _different_ template. Now, that template instantiation happens to be dependent (so internally the linter puts a `this` in front of everything?), which seems to be what causes this. – Max Langhof May 14 '20 at 10:51
  • 1
    Misread, I thought it was base class call (maybe as your linter ;-) ). – Jarod42 May 14 '20 at 10:54
  • 1
    I don't think you ever implicitly capture `this` from a standard point-of-view. The standard seems to always speak about implicitly capturing `*this`, even when you use `[&]`. In particular, in lambda/13 you have *"A lambda-expression with an associated capture-default that does not explicitly capture `*this` [...] is said to implicitly capture the entity (i.e., `*this` or a variable) if the compound-statement: 1) odr-uses (3.2) the entity (in the case of a variable), or 2) odr-uses (3.2) `this` (in the case of the object designated by `*this`), or 3) [...]"*. (N4594) – Holt May 14 '20 at 10:57
  • @dfri C++17 allows capturing `*this` (i.e. the lambda operating on a copy of the class, not just holding a copy of the `this` pointer), whereas C++14 did not. That's what that compatibility is essentially about. But the comments here are not the place to unpack that. – Max Langhof May 14 '20 at 11:04
  • @MaxLanghof I interpret the note as applying to particularly to `[&, this]` (and not "`[&, &this]` or `[=, this]` and so on). [P0806r0](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0806r0.html) explicitly visits this example a _reference capture_, not value capture of `*this`: _"If the default capture is reference capture, the hypothetical “`[&, &*this]`” (in real C++: `[&, this]`) is a redundant reference capture_". I still believe your linter is wrong, but I think this note is peculiar. – dfrib May 14 '20 at 11:09
  • 1
    ... the note was added as part of [P0018R3: Lambda Capture of `*this` by Value as `[=,*this]`](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0018r3.html), though, so you're probably right. I will stop unpacking this thread in this comment thread. Thanks for the feedback. – dfrib May 14 '20 at 11:16

0 Answers0