32

According to the answers and comments for this question, when a reference variable is captured by value, the lambda object should make a copy of the referenced object, not the reference itself. However, GCC doesn't seem to do this.

Using the following test:

#include <stddef.h>
#include <iostream>

using std::cout;
using std::endl;

int main(int argc, char** argv)
{
    int i = 10;
    int& ir = i;

    [=]
    {
        cout << "value capture" << endl
             << "i: " << i << endl
             << "ir: " << ir << endl
             << "&i: " << &i << endl
             << "&ir: " << &ir << endl
             << endl;
    }();

    [&]
    {
        cout << "reference capture" << endl
             << "i: " << i << endl
             << "ir: " << ir << endl
             << "&i: " << &i << endl
             << "&ir: " << &ir << endl
             << endl;
    }();    

    return EXIT_SUCCESS;
}

Compiling with GCC 4.5.1, using -std=c++0x, and running gives the following output:

value capture
i: 10
ir: -226727748
&i: 0x7ffff27c68a0
&ir: 0x7ffff27c68a4

reference capture
i: 10
ir: 10
&i: 0x7ffff27c68bc
&ir: 0x7ffff27c68bc

When captured by copy, ir just references junk data. But it correctly references i when captured by reference.

Is this a bug in GCC? If so, does anyone know if a later version fixes it? What is the correct behavior?

EDIT

If the first lambda function is changed to

[i, ir]
{
    cout << "explicit value capture" << endl
         << "i: " << i << endl
         << "ir: " << ir << endl
         << "&i: " << &i << endl
         << "&ir: " << &ir << endl
         << endl;
}();

then the output looks correct:

explicit value capture
i: 10
ir: 10
&i: 0x7fff0a5b5790
&ir: 0x7fff0a5b5794

This looks more and more like a bug.

Community
  • 1
  • 1
jakar
  • 1,031
  • 1
  • 11
  • 22
  • 2
    Intel 11.1 behaves as expected (prints 10). GCC appears to still have the bug in 4.6.0 and 4.6.1, but 4.7.0-20110618 fails to compile the program with an error about "non-trivial conversion in assignment", they must be working on that. – Cubbi Jun 30 '11 at 03:50
  • Curious off-topic: Why would you ever capture "everything" `=` rather than specifically say what you want? Wouldn't that be more efficient? – Kerrek SB Jun 30 '11 at 10:11
  • 5
    @Kerrek: As long as the same type of capture is used for all variables, capturing everything and specifically listing names should be equivalent, shouldn't they? From what I understand, only the used variables from the immediate enclosing scope are captured. – jakar Jun 30 '11 at 15:39
  • @Cubbi: That's good that they seem to know about the problem, but I'm bummed that it's still broken in GCC 4.6. Explicitly listing the capture arguments is an easy enough workaround. – jakar Jun 30 '11 at 15:51

2 Answers2

8

This has just been fixed in gcc-4.7 trunk and gcc-4.6 branch. These should be available in gcc-4.7.0 (a while from now - still in stage 1) and gcc-4.6.2 (alas 4.6.1 just came out.)

But the intrepid could wait for the next snapshots or get a subversion copy.

See audit trail for details.

emsr
  • 15,539
  • 6
  • 49
  • 62
  • 1
    Wierd thing is that, with gcc-4.9.3 and clang-3.5.0, is the type. The address of the copied things is different, as expected. But, the `decltype` of the copied value is still a reference type. E.g.: why does the last static_assert succeed [on coliru here](http://coliru.stacked-crooked.com/a/be71043e80a39ab6)? – Aaron McDaid Jul 28 '15 at 15:31
  • It could be that the closure type has a member `int & r` that is initialized by a *copy* of the enclosing capture r. I rather hope that is the case. I'll check it out. – emsr Jul 29 '15 at 22:14
  • further experiments have left my more confused. The `decltype` of the copied value is `int&`, as already discussed. But I get an error when I try to assign to it (".. read only..."). I'm not surprised by the error, as a (non-`mutable`) lambda should have non-modifiable copies (when things are copied in). But that reminded me that the `decltype` should be `const int` or `const int &`. The only explanation I can come up with is some sort of bug in `decltype`, where it looks up the wrong `r` - the `r` in the surrounding scope, not the `r` copy in the lambda. – Aaron McDaid Jul 30 '15 at 07:52
  • 1
    The `*this` in the closure type is what's `const`. Or rather the call operator of the closure is a `const` method. The members don't need to be `const`. I suppose in principal that if you could access the lambda object you could change but - I haven't read the standard - I'm pretty sure all the captures are private members. – emsr Jul 30 '15 at 14:31
  • good point! That explains the lack of `const`. But the presence of `&` still confuses me. I would have thought that the copy would have a non-reference decltype. It's as if the lambda has a member that holds the copy, and also has another reference member (that we see) that references the first member. – Aaron McDaid Jul 30 '15 at 14:38
4

Compiled with VS 2010 gives:

value capture
i: 10
ir: 10
&i: 0012FE74
&ir: 0012FE78

reference capture
i: 10
ir: 10
&i: 0012FF60
&ir: 0012FF60

Looks like a bug for me.

Sasha
  • 846
  • 5
  • 9
  • 1
    Yeah, this whole thing started with code that I wrote in windows and built with MSVC. Then I tested it in linux, and it failed. So I narrowed it down this problem, which does make it seem like GCC has a bug. Hopefully, it's very specific to the version on my machine. – jakar Jun 30 '11 at 03:12
  • 1
    @jakar: it is likely a bug, C++0x support is experimental in most compilers for two reasons: 1) a number of features have not made it yet, 2) a number of features were implemented and *then* changes were made in the Standard. As a consequence, don't write C++0x code if you aim at portability. – Matthieu M. Jun 30 '11 at 06:34
  • 1
    @Matthieu: I understand that it's still experimental and accept that risk. I guess that someone has to be the guinea pig to get it out of that stage. Some of the features are really, really useful. Besides, a decent subset of C++0x has been implemented in GCC for awhile now, and MS seems to think that a similar subset is ready for prime time in their compiler. – jakar Jun 30 '11 at 07:15
  • 1
    @jakar: the trouble is, gcc, MS and Clang each have implemented a different subset, and even the subset implemented sometimes differ because they were not based on the same draft of the Standard (or because of outright bugs/designs decision). It's important to actually implement the functionality, to get feedback and see if they work out, before standardizing them of course! However if you need to work with multiple compilers, I would advise refraining from using the most "advanced" features of C++0x. Tuples and variadic templates seem to work pretty fine on clang and gcc, lambdas do not :/ – Matthieu M. Jun 30 '11 at 08:31