Wednesday, May 22, 2013

Lambdas vs. Closures

In recent days, I've twice found myself explaining the difference between lambdas and closures in C++11, so I figured it was time to write it up.

The term "lambda" is short for lambda expression, and a lambda is just that: an expression. As such, it exists only in a program's source code. A lambda does not exist at runtime.

The runtime effect of a lambda expression is the generation of an object. Such objects are known as closures.

Given

  auto f = [&](int x, int y) { return fudgeFactor * (x + y); };

the blue expression to the right of the "=" is the lambda expression (i.e., "the lambda"), and the runtime object created by that expression is the closure.

You could be forgiven for thinking that, in this example, f was the closure, but it's not. f is a copy of the closure. The process of copying the closure into f may be optimized into a move (whether it is depends on the types captured by the lambda), but that doesn't change the fact that f itself is not the closure. The actual closure object is a temporary that's typically destroyed at the end of the statement.

The distinction between a lambda and the corresponding closure is precisely equivalent to the distinction between a class and an instance of the class. A class exists only in source code; it doesn't exist at runtime. What exists at runtime are objects of the class type.  Closures are to lambdas as objects are to classes. This should not be a surprise, because each lambda expression causes a unique class to be generated (during compilation) and also causes an object of that class type--a closure--to be created (at runtime).

Scott

PS - I noted above that a closure is typically destroyed at the end of the statement in which it is created.  The exception to this rule is when you bind the closure to a reference. The simplest way to do that is to employ a universal reference,

  auto&& rrefToClosure = [&](int x, int y) { return fudgeFactor * (x + y); };

but binding it to an lvalue-reference-to-const will also work:

  const auto& lrefToConstToClosure = [&](int x, int y) { return fudgeFactor * (x + y); };

11 comments:

  1. Extremely useful Scott thank you. How goes the book?

    ReplyDelete
  2. @Tom: I'm still working on it. Things got slowed down by the arrival of the C++14 CD, because I'm certainly not going to ignore the impact of a 2014 standard revision on a book I expect to carry a 2014 copyright date. (That is likely to include books published as early as September 2013.) And the title may change to something like Effective C++11/14. But I'm still plugging away at the project :-)

    ReplyDelete
  3. Hi Scott,

    you said "the closure is typically destroyed at the end of the statement in which it is created". Do you mean that here:

    auto f = [&](int x, int y) { return fudgeFactor * (x + y); };

    f is destroyed just after the end of the statement? Then, why in the following code (also here http://ideone.com/JHcPql):

    struct PrintD
    {
    ~PrintD()
    {
    cout << "dtor" << endl;
    }
    };

    int main()
    {
    PrintD d;
    auto f = [d](){
    d;
    };

    cout << "--" << endl;

    return 0;
    }

    "--" is printed before "dtor" (I would expect all the copied objects are destroyed with the closure)? Probably I didn't understand very well.

    Thanks!

    Marco

    ReplyDelete
  4. Hello,

    I was first surprised by your definition considering the lambda expression only in the syntax point of view and the closure as its 'runtime' counterpart.

    Usually, I used to find definition of lambda expression being (to be simple) the definition of an anonymous function and a closure beeing the association of a function and an environment (i.e. mainly captures).

    But in fact, that's true that the environment mainly makes sense at runtime. So its exactly the same thing as you said : lambda as the definition and closure as the definition plus its environment i.e. its instanciation.

    Your paper has been for me the opportunity to check how the STD defines the terms... And it seems that Mr Meyers was holding the pencil :D

    Thanks for your paper.

    Best regards

    ReplyDelete
  5. Hi Scott,

    So are there any benefits to binding the closure vs. copying it?

    When would binding it be appropriate?

    Thanks

    ReplyDelete
  6. Marco, your ideone example uses GCC which is smart enough to elide the copy. If you compile with -fno-elide-constructors you'll see the behaviour Scott describes.

    *Conceptually* there's a copy here too:

    Foo f = Foo();

    but typically that gets elided too. The compiler should do the same thing for closures created by lambda expressions.

    ReplyDelete
  7. Hi Jonathan, you're right, I didn't think of copy-elision here. Thanks!

    Marco

    ReplyDelete
  8. @Marco: Remember that f is not the closure. f is a copy of the closure. So the closure is destroyed at the end of the statement, but f is destroyed at the end of the scope in which it's created.

    I mentioned that that the copying of the closure could be optimized to a move, but I forgot to mention that in this example, the copying operation could be eliminated entirely by copy elision. Fortunately, Jonathan Wakely pointed that out in his comment.

    ReplyDelete
  9. @Kory Draughn: I don't think that binding a closure to a reference is generally a practical thing to do. My motivation for showing how to do it was to demonstrate that it is possible to get your hands on a closure, if you really want to.

    In cases where you are creating a new variable to hold a copy of the closure, Johathan Wakely has already pointed out that copy elision can make that at least as efficient as binding the closure to a reference. When passing closures to generic functions (e.g., STL algorithms), the convention is to pass them by value, so using reference parameters for them would be unconventional.

    In C++14, lambdas support artibrary initializers for data in the closure (this is what enables capture-by-move, but the feature itself is much more general), so I suppose that binding a closure to a reference might be useful if you created a closure with data members that were neither copyable nor movable. But such closures would be...strange.

    ReplyDelete
  10. Scott, one question that remains unanswered here is... why should one care? I mean beside being technically wrong... I mean when I talk to my coworkers I use lambda always(not often though) when we discuss code. Am I missing something here or there is nothing to gain practically from this new knowledge?

    ReplyDelete
  11. @Anonymous: The purpose of language is communication, and as long as you and your coworkers understand one another, it doesn't matter if you draw a distinction between lambdas and closures. Many people call both a lambda and live happy, fulfilling lives. In my experience, the more technical the discussion (especially as regards compilation, code generation, and runtime entities), the more important it is to be precise about what you're referring to. Lambdas occupy no data memory at runtime, for example, though they may occupy code memory. Closures occupy data memory, but not code memory.

    ReplyDelete