Pages

Saturday, 5 March 2016

lambda expression in C++11 and C++14

Lambda expressions are available from C++11 onwards.

The simplest of the lambda expression is
[] {};
which does nothing.

The syntax of a lambda expression is,
[ /*capture-list*/ ] ( /*params*/ ) mutable /* optional*/ /*exception*/ /*attribute*/ -> /*return type*/ { /*body*/ }
capture-list list of comma separated captures
params list of comma separated parameters
mutable allows body to modify parameters captured by value
exception exception specifications
attribute attribute specifications

You can think of lambda expression as a functor. For easy understanding you can imagine that the lambda expression is implemented as some thing like,
class ClosureType
{
    /* members variables are identified using capture list */
public:
    ClosureType(/*capture list*/)
    {}

    /* non-mutable lambda */
    return-type operator(/* parameter list*/) const {/*body*/}

    /* mutable lambda */
    return-type operator(/* parameter list*/) {/*body*}
};

Simple examples

The following code snippet will print Hello from Lambda
[]{
    std::cout << "Hello from Lambda" << std::endl;
}();

Capture list

To access variables declared outside the lambda expression you can use capture list.
int n = 10;

[n]{
    std::cout << "n = " << n << std::endl;
}();
Capturing all automatic variables,
int n = 10;
int m = 20;

[=]{
    std::cout << "n = " << n << " m = " << m << std::endl;
}();
No need to capture global variables, they are accessible in lambda expressions. The above method access variables by value.

To access by reference use & as shown below,
    int n = 10;
    int m = 20;

    [n, &m]{
        std::cout << "n = " << n;
        m = 10;
    }();


    std::cout << " m = " << m << std::endl;
Output of the above program is n = 10 m = 10

Just as we done earlier use just & to access all automatic variables by reference.

this variable can be accessed in the lambda expressions if we add this in the capture list as [this].

capture expressions - C++14

C++14 allows captured members to be initialized with arbitrary expressions. A simple example would be,
auto l = [v = 1] {std::cout << v; };
l();
We can also use reference in the expression as,
int y;
[&x = y]{}

 

Parameters

Paremeters can be specified as you would do for a function. For example the following code will output 1, 2
auto l = [] (int a, int b) {std::cout << a << " " << b; };
l(1, 2);
Can I use template like I do with function? No. But in C++14 you can use auto for parameters as shown below,
auto l = [] (auto a) {std::cout << a ; };
l(1);

 

Return value

If you do not specify any return type will be deduced from the return statement,
auto increment = [] (int i) {return ++i;};
std::cout << increment(1) << std::endl;
The above code can also be declared as,
auto increment = [] (int i) ->int {return ++i;};
std::cout << increment(1) << std::endl;
We can also use decltype in return type.
auto increment = [] (int i) ->decltype(i) {return ++i;};
std::cout << increment(1) << std::endl;
In C++14 the return type can also be auto

Recursive lambda expression

We can also have recursion with lambda expression. In this case we cannot use auto to store lambda variable, but fill have to use std::function. An example is shown below,
#include <iostream>
#include <functional>

std::function<int(int)> factorial = [] (int n) -> int {
    if (n <= 1) {
        return 1;
    }

    return factorial(n-1) * n;
};

int main(int argc, char ** argv)
{
    std::cout << factorial(4) << std::endl;
    return 0;
}

Nested lambda expression

Lambda expression can also be nested.
#include <iostream>

int main(int argc, char ** argv)
{
    int i = 1;

    auto outer = [=] {
        int j = 2;
        auto inner = [=] {
            std::cout << "inner : " << i << " " << j;
        };
        inner();
        std::cout << "outer : " << i << " " << j;
    };

    outer();

    return 0;
}
Output of the above program is inner : 1 2outer : 1 2

Lambda expression as function parameter

There are many ways of declaring function which takes lambda expression as parameter, some of them are shown below.
#include <iostream>
#include <functional>

void func1(std::function<void(std::string const &)> const & l)
{
    l("from func1");
}

template <typename T>
void func2(T l)
{
    l("from func2");
}

// if C++14 is supported
#if __cplusplus > 201103L
void func3(auto l)
{
    l("from func3");
}
#endif

int main(int argc, char **argv)
{
    auto l = [] (std::string const & msg) {
                    std::cout << msg << std::endl;
                };

    func1(l);
    func2(l);

#if __cplusplus > 201103L
    func3(l);
#endif

    return 0;
}

Function returning lambda expression

Examples for function returning lambda expressions are given below,
#include <iostream>
#include <functional>

std::function<void()> func1()
{
    return [] {
                std::cout << "from func1" << std::endl;
              };
}

// In C++14 you don't have to specify return type
auto func2() -> std::function<void()>
{
    return [] {
                std::cout << "from func2" << std::endl;
              };
}

int main(int argc, char **argv)
{
    func1()();
    func2()();

    return 0;
}

Dangling reference

We have to make sure that the variables captured using reference is alive (life time is not ended).
As an example the below given programme has an illegal memory access. It is trying to access variable i after its life time is over.
#include <iostream>
#include <functional>

std::function<void()> func()
{
    int i = 10;
    return [&] {
                std::cout << i << std::endl;
              };
}

int main(int argc, char **argv)
{
    func()();

    return 0;
}

Sizeof lambda expressions

Size of lambda expressions might vary according to the compiler. If the lambda expression does not capture any variables then its size will mostly be 1. If it captures variables the size of lambda expression will the total size of the variables it captured. The output of the below program when compiled with gcc is 1 8.
#include <iostream>

int main(int argc, char ** argv)
{
    int i = 1;
    int j = 2;

    auto l1 = [] {
        std::cout << "l1";
    };

    auto l2 = [=] {
        std::cout << "l2 : " << i << j;
    };

    std::cout << sizeof(l1) << " " << sizeof(l2) << std::endl;

    return 0;
}

Please visit http://blog.trsquarelab.com/2015/05/compiling-c11-and-c14-programs-with-gcc.html for information on how to compile with C++11 and C++14 support in Ubuntu using GCC

No comments:

Post a Comment