Friday, May 4, 2012

c++11 lambda

c++11 lambdas

Lambdas are an excellent and long awaited addition to the c++ language. Lambdas where simply not possible before but the Boost lib offered a rather passable set of macros that let you get away in-lining a kin of lambda. (basically it was a series of functional objects)

The lambda syntax however makes my skin itch a bit. It is yea another reuse of the bracket characters - [](){} - and one that is potentially lowering the readability of the code and introducing coding errors as a result. I would have preferred something a bit more clear and obvious.

Firstly the basic lambda syntax is [capture](parameters)->return-type {body}. This is often short handed the most basic form when people blog and type about it which is []() { .... }


The first [] pair forms a "capture". The capture is run one time at and in the context of the lambdas creation(ie the point where you put the code). There several convenient shortcuts that grab the various variables from the current context automatically. Consider these to be members (and constructor params) of the resulting lambda object.

The next () pair forms the "parameters" these are the same as the parameters of a function definition. This resembles the operator(...) function call of a functional object or the parameter list of a function pointer. Note that these ARE surprisingly optional, but generally they are not left out.

The difference between capture and parameters is rather subtle (or obvious depending on your point of view or experience with it to date). It can at can seem like overkill or just outright fluff when you are constantly using lambdas in the local context. However the key point to realize is that if you pass the resulting lambda out of the current context without executing it then the "capture" has already completed for the context that you made it in and passed it out of. As a result the parameters in the capture must still be IN CONTEXT at the point of the Lambdas actual execution (or the capture was made by value). Parameters on the other hand are filled in when the Lambda is actually executed.

The -> forms the return type. It is optional and often left out. However it can become quite important when templates are used with lambdas otherwise the compiler can get terribly confused with which of the specializations are supposed to be put in place. I assume this is just be a problem with the maturity level of the current gen of c++11 compilers.

And of course the {} forms the body of the code. The same scoping rules apply here as if you coded the object as a completely separate member function of a functional object. So keep in mind that you can not reach outside to variables that where not passed in through the capture or params.

Ok an example: The little known summing of numbers.. zzZZZ.. ha wadaimiss...


// comile with 
//  g++  -std=c++11 lambda.cpp

#include <iostream>
#include <algorithm>
#include <vector>
#include <functional>

void examplePassByValue(int* start, int* end)
{
  int stuff[]={1,2,3,4,5,6,7,8,9};
  int sum = 0; 
  std::for_each(stuff, stuff+(sizeof(stuff)/sizeof(int)), [sum] (int v) 
    {
      std::cout << v << "\n";
    // This is passed by const value (os its BAD.. if you try to modify it.. )
    //sum += v;
    });
}

void examplePassByRef(int* start, int* end)
{
  int sum = 0; 
  std::for_each(start, end, [&sum] (int v )
  {
    std::cout << v << "\n";
    sum += v;
  });
  std::cout << " sum is : "<< sum << "\n";
}

void examplePassByRefImplicatScoped(int* start, int* end)
{
  return;

  // pass by ref of the entire implicat scope
  int sum = 0; 

  std::for_each(start, end, [&] (int v) 
  {
    std::cout << v << "\n";
    sum += v;
  });
  std::cout << " sum is : "<< sum << "\n";
}

// and now the fun...
template <class T>
void filterTo(T* start,
       T* end,
       std::function<bool (const T&)> filter,
       std::function<void (const T&)> action
       )
{ 
  std::for_each(start, end, [=] (T v) { if ( filter( v ) ) action( v ); });
}

void exampleStdFunctionLambdaNoClosure(int* start, int* end)
{
  //lambda compiling to a function pointer compatible with std:function
  std::function<bool (const int&)> filter
    = [](const int& v)->bool { return v%2; };
  std::function<void (const int&)> action
    = [](const int& v)->void { std::cout << "filt odd: " << v << "\n"; };

  filterTo(start, end,
    filter, action);
}

void exampleLambdaNoFunctionTemplateWeakness(int* start, int* end)
{
//  //This is the same as above and should work however the curent 
//  // compiler is  very poor at solving this.. so it dies
//  filterTo(start, end,
//    [](const int& v)->bool { return v%1; },
//    [](const int& v)->void { std::cout << v << "\n"; });
}

void exampleStdFunctionLambdaClosure(int* start, int* end)
{
  //evil: lambda closure is creates a class object not a function pointer
  // this can still wrap up in the std::function 
  int sum = 0;
  std::function<bool (const int&)> filter
    = [](const int& v)->bool { return v%2; };
  std::function<void (const int&)> action
    = [&sum](const int& v)->void {  sum += v; std::cout << "run sum:" << sum << "\n"; };

  //lambda compiling to a function pointer compatible with std:function
  filterTo(start, end,
    filter, action);

  std::cout << "Tot sum:" << sum << std::endl;
}

int main()
{
  int stuff[]={1,2,3,4,5,6,7,8,9};
  int* end = stuff + (sizeof(stuff)/sizeof(int)); //warning this is pointer arithmetic
 
  //suming examples
  examplePassByValue(stuff, end);
  examplePassByRef(stuff, end);
  examplePassByRefImplicatScoped(stuff, end);

  //find all odds..
  exampleStdFunctionLambdaNoClosure(stuff, end);
  exampleStdFunctionLambdaClosure(stuff, end);
  
  return 0;
}

Since this is a bit of a mess to run here is the make to build and run it (set BASE) to match your mingw 4.7 install path
BASE=/c/tools/mingw47
export PATH:=${BASE}/i686-w64-mingw32/lib:${BASE}/bin:${PATH}

all: run

lambda.exe: lambda.cpp
 g++ -std=c++11 lambda.cpp -o lambda.exe

run: lambda.exe
 lambda.exe



Refer: http://www.cprogramming.com/c++11/c++11-lambda-closures.html

1 comment:

  1. Interesting... but wasn't it called lambda ?

    ReplyDelete