33

Have I lost my mind? Was this always permitted?

#include <iostream>

int main()
{
    auto& os = std::cout;

    auto write = []()
    {
        os << "what\n";
    };

    write();
}

I'm using:

Apple LLVM version 10.0.0 (clang-1000.10.44.4)
Target: x86_64-apple-darwin17.7.0

Though also see on Coliru:

(live demo)

I always thought an empty capture would not capture anything.

Indeed, MSDN says:

An empty capture clause, [ ], indicates that the body of the lambda expression accesses no variables in the enclosing scope.

Further research suggests that this is in fact okay for capturing const things (which I also didn't know, but whatever), but os is not const (no reference is! though it is immutable…).

I came across this when turning on -Wextra and noticing that Clang thought a &os capture (which is present in my real code) was unnecessary. Removing it I was staggered to find the build worked.

curiousguy
  • 8,038
  • 2
  • 40
  • 58
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • 2
    possible duplicate: https://stackoverflow.com/questions/43827651/can-a-lambda-capturing-nothing-access-global-variables – papagaga Nov 21 '18 at 15:26
  • @papagaga: No, no, no. This is about const automatics. – Bathsheba Nov 21 '18 at 15:26
  • global variables are in scope without capture – papagaga Nov 21 '18 at 15:26
  • 1
    I get source>: In lambda function: :9:9: error: 'os' is not captured os << "what\n"; ^~ (godbolt gcc 7.2 C++17) – Matthieu Brucher Nov 21 '18 at 15:26
  • 1
    @papagaga `os` is not global. Although the thing it refers to is. ..... ah. – Lightness Races in Orbit Nov 21 '18 at 15:26
  • 4
    Oddly works with gcc 8.2... – Matthieu Brucher Nov 21 '18 at 15:27
  • 2
    [I've tripped up on this before](https://stackoverflow.com/q/47592474/560648). Except here the behaviour seems to be the inverse. – Lightness Races in Orbit Nov 21 '18 at 15:28
  • 2
    msvc 19.14 [rejects it](https://godbolt.org/z/wlyO1r) – François Andrieux Nov 21 '18 at 15:28
  • clang also accepts this, so it's basically inference that makes it work? – Matthieu Brucher Nov 21 '18 at 15:29
  • 8
    Interesting. This looks like an optimization bug, where the compiler "sees through" the reference before ensuring that the reference itself is okay to use. – Quentin Nov 21 '18 at 15:30
  • yes, so moving the lambda away in a function makes it fail, so it's inference (perhaps not optimization?) – Matthieu Brucher Nov 21 '18 at 15:31
  • no need to capture if it "is a non-local variable or has **static** or thread local storage duration". Could it be that? – papagaga Nov 21 '18 at 15:32
  • @papagaga Guess it depends whether we consider `os` to be a local variable with automatic storage duration, or equivalent to its referent. Though as noted above results seem to vary. (I'm going to have to capture `[&]` to avoid the warning and get a good build in all cases, I think.) – Lightness Races in Orbit Nov 21 '18 at 15:33
  • @rustyx: it seems that references must be captured independently when they don't refer to a global object. For instance: `#include int main() { int i = 5; auto& os = i; auto write = [&i]() { std::cout << os; }; write(); }` will trigger – papagaga Nov 21 '18 at 15:49
  • 2
    @rustyx : Indeed they are not variables, but lambdas do not capture variables, they capture _entities_, and all of values, objects, and references constitute entities ([basic]/3). – ildjarn Nov 21 '18 at 15:53
  • 6
    There's a [defect report](https://bugs.llvm.org/show_bug.cgi?id=34865) opened covering that case and it seem not restricted for `std::cout` – Jans Nov 21 '18 at 15:56
  • 3
    @Jans and that is worthy of an answer! – SergeyA Nov 21 '18 at 16:07
  • 2
    IMO the code should be allowed: `int &r = n;` indicates that `r` and `n` are two names for the same variable. There shouldn't be any case where the expression `r` behaves differently to the expression `n`. The lambda can use `n` without capture so it should be able to use `r` too. – M.M Nov 21 '18 at 21:09

1 Answers1

23

There's an open clang report that cover the case of implicit capture of references by lambda expressions, this is not limited to std::cout but to references variable that are found to refer to constant expressions.

For more reference, the backing defect report on the CWG is CWG-1472

EDIT:

Based on @Rakete1111 comment, I should have pointed out explicitly that clang is right in accepting the code, which is the result of applying the CWG defect mentioned above. The report was reopened because of diagnosis location not because they were wrong about the acceptance

Jans
  • 11,064
  • 3
  • 37
  • 45
  • 1
    That's the badger! Thanks Jans. – Lightness Races in Orbit Nov 21 '18 at 17:21
  • 1
    Are you implying that clang is wrong to accept the code? – Rakete1111 Nov 22 '18 at 06:33
  • 1
    @Rakete1111 No, As Richard said in the report, when the reference variable is found to refer to constant expression, the referenced variable is used instead of the reference variable itself, the issue was reopened by diagnosis location not because they were wrong about the reference. – Jans Nov 22 '18 at 14:51