Quantcast
Viewing latest article 3
Browse Latest Browse All 5

Understanding C++ 0x: Lambda functions

I’ll make a bet with you. I’m going to guess that, quite frequently, when coding in C++, you have the following thought: “I really need a function that does X, and the function is really only a couple lines long, but now I have to go declare and define the damn thing somewhere …” There’s no denying the fact that creating a new function in C++ comes with a certain amount of tedium and mental overhead, and there are just some times where the code within the function doesn’t seem like it would justify defining the function in an entirely different place. Well, have no fear, the C++ standards committee shares your concerns, and they’ve imported a nifty feature present in many other languages called a lambda function, and let me tell you, it is sweet.

What is a lambda function?

A lambda function is essentially an anonymous function (a function without a name) that’s defined inline. It’s incredibly useful for when you need a small function that doesn’t really seem to justify declaring and defining a normal function. The typical example where a lambda function is handy is for something like a comparison being passed to std::sort. For example:

#include <algorithm>
using namespace std;

struct Apple
{
   Apple(double weight, int age) :
      m_weight(weight),
      m_age(age)
   {}

   double m_weight; // The apple's weight, in kg
   int m_age; // The apple's age, in days
};

int main()
{
   vector<Apple> myApples;
   myApples.push_back(Apple(0.50, 35));
   myApples.push_back(Apple(0.75, 40));
   myApples.push_back(Apple(0.35, 37));

   // Sort the apples by their age
   sort(myApples.begin(),
        myApples.end(),
        [](const Apple &a, const Apple &b) -> bool
        {
           return (a.m_weight < b.m_weight);
        });

   return 0;
}

Now you might be asking yourself: “What’s that weird third parameter to the sort function?” Well that, my friend, is a lambda function. The syntax for them is something like this:

// You can omit the -> ReturnType if the function has no return type
[](Type1 parameter1, Type2 parameter2, ...) -> ReturnType
{
   // Function contents
}

// You can invoke the lambda function right away by doing...
[](Type1 parameter1, Type2 parameter2) -> ReturnType
{
   // Function contents
}(); // <-- Note the parentheses

You can pass the function any number of parameters, just like a normal function. It’s pretty easy to remember and incredibly handy.

But wait, there’s more! To understand how the rest works, you have to understand the idea of closure. You see, lambda functions can refer to variables that are outside of the function, even if it doesn’t really seem like those variables should be in scope. The set of variables that the lambda function has access to is called its closure. Now that may sound very strange, but it’s not very hard to use once you see it used. It looks something like this:

int appleCount = 0;
int orangeCount = 2;
[&appleCount, =orangeCount]()
{
   ++appleCount;
   ++orangeCount;
}();
// appleCount == 1
// orangeCount == 2

You see, by putting &appleCount between the square brackets, we told the program to add appleCount into our function’s closure, but give the function access by reference. Just like a normal function, when a variable is passed by reference into a lambda function’s closure, any changes made to the variable within that lambda function persist after the completion of the call. However, by putting =orangeCount between the square brackets also, we told the program to add orangeCount into our function’s closure as well, but orangeCount is only passed by value, not reference. Because we’re only passing a copy of orangeCount to the function, the changes made to orangeCount within the function don’t persist after the close of the lambda function.

Here are a few examples of different types of closures that a lambda function could have:

[] // No access to outside variables
[&orangeCount] // orangeCount is passed by reference
[=appleCount] // appleCount is passed by value
[&orangeCount, =appleCount] // orangeCount is passed by reference, appleCount by value
[&orangeCount, =] // orangeCount is passed by reference, all other variables in scope are passed by value
[=appleCount, &] // appleCount is passed by value, all other variables in scope are passed by reference

Using the fancy function pointer syntax that we learned, we can now store the lambda function that we made and use it later.

int DoThisFunction(std::function<int (int, int)> func)
{
   int result = func(3, 4);
   return result;
}

int main()
{
   int z = 0;

   // Create the lambda function and store it into myFunction
   // We captured z by reference
   std::function<int (int, int)> myFunction = [&z](int x, int y) -> int
   {
      ++z;
      return x * y;
   };

   int a = DoThisFunction(myFunction);

   // Value of z is now 1
   // Value of a is now 12
}

Note the craziness of what just happened: we made the lambda function at a point when we had access to z. We passed that lambda function to a function that didn’t have access to z. Then we called the function, changing the value of z even though z was not in scope when we called the function. Very cool stuff.

Clearly this is just an introduction to the types of things that you can do with lambda functions. Honestly, entire books could be written about the possibilities. The important thing, though, is that you understand the basics. So go forth and program!


Viewing latest article 3
Browse Latest Browse All 5

Trending Articles