C++11 lambda returning lambda

10,599

Solution 1

Your code has a bug in that it contains a dangling reference; the c reference will refer to the local variable in the outer lambda, which will be destroyed when the outer lambda returns.

You should write it using a mutable by-value lambda capture:

auto a = []() {
    int c = 0;
    return [=]() mutable {
        cout << c++;
    };
};

This relies on a post-standard extension to allow multiple statements in a return-type-deducing lambda; Is there a reason on not allowing lambdas to deduce the return type if it contains more than one statement? The easiest way to fix it is to supply a parameter so that the lambda contains only a single statement:

auto a = [](int c) {
    return [=]() mutable {
        cout << c++;
    };
};

Unfortunately default parameters aren't allowed in lambdas, so you'd have to call this as a(0). Alternatively at the cost of readability you could use a nested lambda call:

auto a = []() {
    return ([](int c) {
        return [=]() mutable {
            cout << c++;
        };
    })(0);
};

The way this works is that when a executes the inner lambda copies all the referenced variables into an instance of its closure type, which here would be something like:

struct inner_lambda {
    int c;
    void operator()() { cout << c++; }
};

The instance of the closure type is then returned by the outer lambda, and can be invoked and will modify its copy of c when called.

Overall, your (fixed) code is translated to:

struct outer_lambda {
    // no closure
    struct inner_lambda {
        int c;    // by-value capture
        // non-const because "mutable"
        void operator()() { cout << c++; }
    }
    // const because non-"mutable"
    inner_lambda operator()(int c) const {
        return inner_lambda{c};
    }
};

If you left c as a by-reference capture, this would be:

struct outer_lambda {
    // no closure
    struct inner_lambda {
        int &c;    // by-reference capture
        void operator()() const { cout << c++; } // const, but can modify c
    }
    inner_lambda operator()(int c) const {
        return inner_lambda{c};
    }
};

Here inner_lambda::c is a dangling reference to the local parameter variable c.

Solution 2

It's a natural limitation of C++ that a lambda which captures by reference can't use the captured variable any more, once the variable no longer exists. So even if you get it to compile, you can't return this lambda from the function in which it appears (that also happens to be a lambda, but that's irrelevant), because the automatic variable c is destroyed on return.

I think the code you need is:

return [=]() mutable {
    cout << c++;
};

I haven't tested it and I don't know what compiler versions support it, but that's a capture-by-value, with mutable to say that the captured value can be modified by the lambda.

So each time you call a you get a different counter with its own count starting from 0. Each time you call that counter, it increments its own copy of c. As far as I understand Javascript (not far), that's what you want.

Solution 3

I think the problem is that the compiler cannot deduce the return type of the outer lambda (that assigned to a) because it consists of more than a simple one line return. But unfortunately there is also no way to explicitly state the type of the inner lambda. So you will have to return a std::function, which comes with some additional overhead:

int main()
{
    int c;
    auto a = []() -> std::function<void()> {
        int c = 0;
        return [=]() mutable {
            std::cout << c++;
        };
    };
    return 0;
}

And of course you have to capture by-value, like Steve already explained in his answer.

EDIT: As to why the exact error is that it cannot convert the returned inner lambda to void(*)() (pointer to void() function), I only have some guesses because I don't have much insight into their lambda implementation, which I'm not sure is that stable or standard-conformant at all.

But I think VC at least tries to deduce the return type of the inner lambda and realizes that it returns a callable. But then it somehow incorrectly assumes this inner lambda to not capture (or they are not able to determine the inner lambda's type), so they just make the outer lambda return a simple function pointer, which would indeed work if the inner lambda wouldn't capture anything.

EDIT: And like ecatmur states in his comment, returning a std::function is even neccessary when making an actual get_counter function (instead of a lambda), since normal functions don't have any automatic return type deduction.

Solution 4

The first thing you should know is that even if you get the syntax to compile, the semantics are different. In C++ lambdas that capture by reference capture just a plain reference, that will not extend the lifetime of the object bound by that reference. That is, the lifetime of c is bound to the lifetime of the enclosing lambda:

int main()
{
    int c;
    auto a = [](){
        int c = 0;
        return [&](){
            return ++c;
        };
    }();                     // Note: added () as in the JS case
    std::cout << a() << a();
    return 0;
}

After adding the missing () so that the external lambda is evaluated, your problem is that the c that is held by reference in the returned lambda is no longer valid after the evaluation of the full expression.

That being said, it is not too complex to make that work at the cost of an extra dynamic allocation (which would be the equivalent of the JS case):

int main()
{
    int c;
    auto a = [](){
        std::shared_ptr<int> c = std::make_shared<int>(0);
        return [=](){
            return ++(*c);
        };
    }();                     // Note: added () as in the JS case
    std::cout << a() << a();
    return 0;
}

That should compile and work as expected. Whenever the internal lambda is released (a goes out of scope) the counter will be freed from memory.

Solution 5

This works on g++ 4.7

#include <iostream>
#include <functional>                                                                           

std::function<int()> make_counter() {
    return []()->std::function<int()> {
        int c=0;
        return [=]() mutable ->int {
            return  c++ ;
        };  
    }();
}   


int main(int argc, char * argv[]) {
    int i = 1;
    auto count1= make_counter();
    auto count2= make_counter();

    std::cout << "count1=" << count1() << std::endl;
    std::cout << "count1=" << count1() << std::endl;
    std::cout << "count2=" << count2() << std::endl;
    std::cout << "count1=" << count1() << std::endl;
    std::cout << "count2=" << count2() << std::endl;
    return 0;
}

Valgrind doesn't complain about this at all. Every time I call make_counter, valgrind reports an additional allocation and free, so I assume the lambda meta programming code is inserting the allocation code for the memory for variable c (I guess I can check the debugger). I wonder if this is Cxx11 compliant or just g++ specific. Clang 3.0 will not compile this because it doesn't have std::function (maybe I can try using boost function).

Share:
10,599

Related videos on Youtube

Ali1S232
Author by

Ali1S232

Updated on September 14, 2022

Comments

  • Ali1S232
    Ali1S232 about 1 year

    this piece of code is not something unknown to JS developers

    function get_counter()
    {
        return (
            function() {
                var c = 0;
                return function() { return ++c; };
            })();
    }
    

    it basically creates a which creates different enumerators. So I was wondering if same thing can be done in C++11 with new lambda semantics? I ended up writing this piece of C++ which unfortunately does not compile!

    int main()
    {
        int c;
        auto a = [](){
            int c = 0;
            return [&](){
                cout << c++;
            };
        };
        return 0;
    }
    

    so I was wondering if there is a workaround to get it compiled and if there is how can compiler make this code run correctly? I mean it has to create separate enumerators but it should also collect garbage (unused c variables).

    by the way I'm using VS2012 compiler and it generates this error:

    Error   2   error C2440: 'return' : cannot convert from 'main::<lambda_10d109c73135f5c106ecbfa8ff6f4b6b>::()::<lambda_019decbc8d6cd29488ffec96883efe2a>' to 'void (__cdecl *)(void)'    c:\users\ali\documents\visual studio 2012\projects\test\test\main.cpp   25  1   Test
    
    • Ali1S232
      Ali1S232 about 11 years
      @ecatmur vs2012 here I've updated and added the error message I get, besides do you have any idea if that piece of code is safe? I mean is it wasting memory or is there some hidden garbage collector implemented somewhere?
    • Steve Jessop
      Steve Jessop about 11 years
      There's no garbage collector, and it doesn't waste memory. Each time you call a, it will return a new lambda object. You'll have to assign the return value of a to something -- when that something is destroyed, any resources used by the lambda to capture variables are destroyed with it.
    • Xeo
      Xeo about 11 years
      In C++11, you need a body of the form return expr; for return type deduction, which you don't have. This code shouldn't compile in any case without the extended return type deduction rules from post-C++11. And I'd be surprised if VS2012 has those.
    • Christian Rau
      Christian Rau about 11 years
      @Xeo In fact you're the first one (apart from me ;)) to realize this. All other answers so far concentrated on the by-ref capture (which of course is also a bug, but not a compiler error).
    • Walter
      Walter about 11 years
      so there are 2 bugs in your code. one is to return an object with a (potentially) dangling reference, the other is to not specify the appropriate return type (and causing your compiler error).
    • Steve Jessop
      Steve Jessop about 11 years
      @Christian: so what's the explanation for that exact compiler error? Why is MSVC trying to convert the inner lambda type to void(*)void, when the deduced return type of the outer lambda is void?
    • Xeo
      Xeo about 11 years
      @Christian: I noticed before your answer, but I was too lazy to write one, so when you answer popped up, I just instantly upvoted it. :P
    • Xeo
      Xeo about 11 years
      @Steve: One can only wonder. Note that extended return type deduction is a much wanted feature and in fact there's a proposal for it (GCC 4.7 also implements it), but I would really wonder if VS2012 implemented it. And even if they have, void(*)(void) is just.. wrong.
    • Christian Rau
      Christian Rau about 11 years
      @SteveJessop Ask something easier. I don't have that much insight into their implementation of lamdas and in fact doing some neat lambda tricks (as which I would regard a lambda returning a lambda) can easily make the compiler crash (at least in VC2010), so I'm not even sure they're behaving standard conformant here. Maybe the standard even allows automatic return type deduction in this case and VC got half-way there by determining that a callable is returned, but incorrectly assumes the returned lambda to not capture, in which case it is indeed convertible to void(*)void.
  • David Rodríguez - dribeas
    David Rodríguez - dribeas about 11 years
    This has an error: by default capture by value does not allow the modification of the captured object (the implicitly generated operator() is const), which means that the c++ inside the inner lambda will fail to compile. Note that the outer lambda need not be mutable.
  • Xeo
    Xeo about 11 years
    Sadly, with a conforming C++11 compiler, this still won't work since the return type will not be deduced. :/
  • Steve Jessop
    Steve Jessop about 11 years
    @Xeo: really? Isn't it void since there's no trailing-return-type and the body of the lambda isn't a return statement? Anyway, make it return [=]() mutable -> void if it helps. I think this cout << c++; is just for debugging anyway, to match the Javascript it will be return ++c;, which will allow return type deduction (and count from 1 instead of 0).
  • Christian Rau
    Christian Rau about 11 years
    @SteveJessop No, the outer lambda (that assigned to a). See my answer.
  • Walter
    Walter about 11 years
    +1 for touching the dangling reference issue. However what is meant by is only valid? The lambda will simply contain a dangling reference and we have UB.
  • Christian Rau
    Christian Rau about 11 years
    @DavidRodríguez-dribeas Are you referring to an old version (where the outer lambda was not mutable) or is there stil an error? Ah, forget what I said. Found and corrected, thanks.
  • Steve Jessop
    Steve Jessop about 11 years
    @Christian: ah, I see. Yes, so there are two problems to fix and I've only fixed one.
  • David Rodríguez - dribeas
    David Rodríguez - dribeas about 11 years
    I might be wrong, I don't have much experience with lambdas, but the inner lambda captures by value and then attempts to modify that value. That IIRC requires the inner lambda to be mutable.
  • Steve Jessop
    Steve Jessop about 11 years
    @Walter: I was under the impression that it's UB for a lambda to exist at all beyond the lifetime of what it captures by reference, it's not guaranteed that it behaves like it holds dangling pointer (i.e. only gives UB if you access the dangling value). But I can't remember where I got that from, I may be wrong. Either it's UB to do this, or it's UB to do this and then call the inner lambda, both are bad.
  • Steve Jessop
    Steve Jessop about 11 years
    +1 from me, there's a small risk introducing that parameter that someone might specify it when they aren't really entitled to because the ability isn't there by design. But actually it's quite a good extension to the functionality of this counter-generator, so add it to the design and move on :-)
  • Walter
    Walter about 11 years
    @SteveJessop Well, I think it would have better to explicitly mention UB instead of is only valid.
  • Steve Jessop
    Steve Jessop about 11 years
    @Walter: is that because you initially didn't read "is only valid" to mean, "you will get problems that I'm not precisely specifying unless...", or is it because you want me to precisely quantify the invalid-ness of it? If the former then I'll fix for clarity, if the latter then as I've explained, I'm not certain of the precise conditions that trigger the UB so I'll stick to vague warnings.
  • ecatmur
    ecatmur about 11 years
    @SteveJessop turns out default arguments aren't allowed in lambdas (5.1.2:5). Slightly annoying, that.
  • ecatmur
    ecatmur about 11 years
    std::function is a good suggestion, and is necessary if a is to be an actual function.
  • ecatmur
    ecatmur about 11 years
    5.1.2:22 [ Note: If an entity is implicitly or explicitly captured by reference, invoking the function call operator of the corresponding lambda-expression after the lifetime of the entity has ended is likely to result in undefined behavior. —end note ] I don't see any issue with the lambda existing at that point.
  • Steve Jessop
    Steve Jessop about 11 years
    yeah, maybe because lambdas are intended to be captured by std::function, and once you erase the type you've lost the value of the default parameter. Shame you can't have them anyway, for lambdas that aren't type-erased. I guess it wasn't considered a major use case, but as you've shown returning a lambda from a function provides use cases where it's handy to return something that can be called with optional parameters.
  • Steve Jessop
    Steve Jessop about 11 years
    @ecatmur: Since that's only informative, "is likely to" presumably means that it does behave like it holds a dangling pointer, and the function "is likely to" access what it captures, resulting in UB. So, what Walter said.
  • ildjarn
    ildjarn about 11 years
    +1, your 'nested lambda call' approach is exactly how I would have done it.