13

Why does this code compile with GCC (4.9 and 5+), but not with clang (3.5-3.9)?

void test(const int&) { }
int main() {
  const int x = 42;
  auto f = []{ test(x); };
}

I have some vague idea that the discrepancy has to do with ODR (One Definition Rule) usage, but I don't understand that well enough to figure out what's going on here.

Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
Daisy Sophia Hollman
  • 6,046
  • 6
  • 24
  • 35
  • 1
    interestingly clang is happy if you make x static – Richard Hodges Mar 17 '16 at 21:27
  • 3
    @RichardHodges Variables with static storage duration don't need to be captured. – Brian Bi Mar 17 '16 at 21:33
  • Does anyone else think it is a bit silly that one has to explicitely capture context instead of the compiler figuring it out for us? – BitTickler Mar 17 '16 at 21:34
  • 1
    @BitTickler no, explicit capture is a good thing. it prevents accidentally capturing circular references (e.g. when capturing a weak/shared ptr) – Richard Hodges Mar 17 '16 at 21:35
  • @BitTickler That's the whole point of the capture-default specifiers = and & – Daisy Sophia Hollman Mar 17 '16 at 21:36
  • @BitTickler the options are `[=]` and `[&]` , in this exact case it makes no difference but in general the capture methods differ so the compiler could not 'figure it out' – M.M Mar 17 '16 at 21:36
  • What happens if `test` is in another compilation unit, where the compiler can't see into it (and see that it doesn't depend on the identity of its argument)? – Ben Voigt Mar 17 '16 at 21:37
  • @BenVoigt same deal; gcc constructs a local prvalue temporary from `x` and passes a reference to that temporary to `test`. – ecatmur Mar 17 '16 at 21:44
  • @ecatmur I've been in that world, coding objective-c captures. It's a bad, bad world. c++ has it right. – Richard Hodges Mar 17 '16 at 21:50
  • This is a good example of how C++ has too many corner cases. (You're using a variable in a lambda expression, so it needs to be captured. But wait, it's a constant, so it doesn't. But wait, it's actually used by reference, so it does.) – user253751 Mar 18 '16 at 02:12

2 Answers2

16

x is odr-used because it's bound to a reference (test's parameter). It therefore must be captured ([expr.prim.lambda]/13):

If a lambda-expression or an instantiation of the function call operator template of a generic lambda odr-uses ([basic.def.odr]) this or a variable with automatic storage duration from its reaching scope, that entity shall be captured by the lambda-expression.

Violations of this rule, like all other rules in the standard that doesn't say "no diagnostic required" or "undefined behavior", require a diagnostic.

GCC, unfortunately, performs constant folding too early, before it could tell whether it's an odr-use or not. This can lead to problems such as [&]()->const int & { return x; } returning a dangling reference.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • @M.M you could certainly exploit this to produce the same effect though, right? – Daisy Sophia Hollman Mar 17 '16 at 21:39
  • @M.M I'm pretty sure **[class.local]** /1 applies (since a lambda is a local class): *Declarations in a local class shall not odr-use (3.2) a variable with automatic storage duration from an enclosing scope.* – ecatmur Mar 17 '16 at 21:41
  • @ecatmur have you got a reference for that? In N4140 the term "local class" does not appear in the [expr.prim.lambda] section. (although this is moot as TC finds that there is an equivalent rule for lambdas specifically) – M.M Mar 17 '16 at 21:51
  • 1
    @M.M ["The closure type is declared in the smallest block scope, class scope, or namespace scope that contains the corresponding *lambda-expression*."](http://eel.is/c++draft/expr.prim.lambda#3) – T.C. Mar 17 '16 at 21:52
  • @T.C. OK. I guess "An implementation may define the closure type differently" is not allowed to extend to it declaring the closure type at file scope – M.M Mar 17 '16 at 21:58
  • @M.M "from what is described below". That sentence is not "below". – T.C. Mar 17 '16 at 22:00
8

T.C. has the right diagnosis, here's a clearer bit of legal code where clang does the right thing and gcc doesn't:

#include <iostream>

void test(const int&a) { std::cout << "in test() -- " << &a << "\n"; }
int main() {
  const int x = 42;
  std::cout << "in main() -- " << &x << "\n";
  auto f = [&]{ test(x); };
  f();
}

gcc prints different addresses for a capture-by-reference variable than the original!

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • 3
    It's even worse than that; try `int main() { const int x = 42; return [&]{ const int& a = x; const int& b = x; return &a == &b; }(); }` - gcc binds the two references to distinct prvalue temporaries! – ecatmur Mar 17 '16 at 21:53
  • @ecatmur: Sorry for the noise. It's really hard to see where the lambda ends and main continues again, for code in a comment. Yeah, that's evil. – Ben Voigt Mar 17 '16 at 21:56