Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is a generalized lambda capture and why was it created?

Tags:

c++

lambda

c++14

I see a lot of questions in which generalized lambda captures are used for various things, but nothing that explains exactly what they are or why they were added to the standard.

I've read what appears to be the document describing the updated to the standard necessary for generalized lambda captures to exist, but it doesn't really say why they were created, nor does it really give a good summary of exactly how they work. It's mostly just a bunch of dry 'strike this out here and add this language here' stuff.

So, what are they? Why would I want to use one? What rules do they follow? For example, it seems to allow capture expressions. When are these expressions evaluated? If they result in side effects, when do those take effect? If several expressions are evaluated, is there a guaranteed evaluation order?

like image 882
Omnifarious Avatar asked Mar 11 '23 05:03

Omnifarious


1 Answers

As usual, Scott Meyers offers a great explanation of what they are, why you would want to use them, and roughly what rules they follow, in Effective Modern C++ Item 32: Use init capture to move objects into closures. Here is a short summary:

Since C++11 only has by-value and by-reference capture, by-move capture was missing. Instead of adding it, C++14 introduced the generalized lambda capture, also known as init capture. It allows you to specify

  1. the name of a data member in the closure class generated from the lambda, and
  2. an expression initializing that data member.

With this, an init capture is a new and more general capture mechanism, which covers the three above by-* captures and more (but no default capture mode).

Coming back to the main motivation of by-move capture, it can be implemented by an init capture as follows:

auto pw = std::make_unique<Widget>(); 

// configure *pw

auto func = [pWidget = std::move(pw)] { 
    return pWidget->isValidated() && pWidget->isArchived(); 
};

You can simulate an init capture in C++11 with

  • a hand-written class:

    class IsValAndArch {
    public:
        using DataType = std::unique_ptr<Widget>;
    
        explicit IsValAndArch(DataType&& ptr) : pw(std::move(ptr)) {}  
        bool operator()() const { return pw->isValid() && pw->isArchived(); }    
    
    private:
        DataType pw;
    };
    
    auto pw = std::make_unique<Widget>();
    
    // configure *pw;
    
    auto func = IsValAndArch(pw);
    
  • or with std::bind:

    auto pw = std::make_unique<Widget>();
    
    // configure *pw;
    
    auto func = std::bind( [](const std::unique_ptr<Widget>& pWidget)
        { return pWidget->isValidated() && pWidget->isArchived(); },
        std::move(pw) );
    

    Note that the parameter for the lambda is an lvalue reference (because the moved pw within the bind object is an lvalue) and has a const qualifier (to simulate the lambda's constness; so if the original lambda were declared mutable, the const qualifier would not be used).

Some details about init captures is also given in Anthony Calandra's cheatsheet of modern C++ language and library features, for instance that the initializing expression is evaluated when the lambda is created (not when it is invoked).

like image 164
DaveFar Avatar answered Mar 23 '23 02:03

DaveFar