C++ Basics: Understanding Lambda
Tags: Cpp, Programming, Software Engineering, Coding, Software Development

C++ Basics: Understanding Lambda

Photo by Tudor Baciu on Unsplash

Introduction

One of the new features introduced in Modern C++ starting from C++11 is Lambda Expression.

It is a convenient way to define an anonymous function object or functor. It is convenient because we can define it locally where we want to call it or pass it to a function as an argument.

Lambda is easy to read too because we can keep everything in the same place.

In this post, we’ll look at what a lambda is, compare it with a function object (functor), and more importantly understand what it actually is and how to think about it when coding in C++.

Lambda Expressions

This is how we define a lambda in C++:

Lambda Expression — Code by Author

plus_one in this code is a functor under the hood. Let’s now first see what a functor is.

Function Object or Functor

According to Wikipedia, A function object or usually referred to as a functor is a construct that allows an object to be called as if it were an ordinary function.

The keyword here is the “ordinary function”. In C++ we can overload operator () to implement a functor. Here is a functor that behaves the same as our lambda:

A Functor for Plus One — Code by Author

One example of the advantages of using a functor over an ordinary function is that it can access the internal member variables and functions of that object.

It will be clearer when we want to create functions “plus one”, “plus two”, etc. By using a functor we don’t have to define multiple functions with unique names.

Functor Advantage — Code by Author

As you can see, at the caller side it looks like a call to an ordinary function.

How does it look like at the machine level? Well, a functor is an object so it has member variables and member functions. The ordinary function is as follows:

int plus_one(const int value)

{

return value + 1;

}

whereas a functor is as follows:

int PlusOne::operator()(const PlusOne* this, const int value)

{

return value + this->data;

}

Lambdas vs. Functors

If we already have a functor which in some scenarios is better than an ordinary function, why do we need lambda?

Lambda offers a simpler way to write a functor. It is a syntactic sugar for an anonymous functor. It reduces the boilerplate that we need to write in a functor.

To see how lambda simplifies a functor, we create a lambda for our Plus class above.

Lambda for plus operation — Code by Author

We can remove a lot of boilerplate code from our functor above. We know that our functor looks like this:

int PlusOne::operator()(const PlusOne* this, const int value)

{

return value + this->data;

}

What about our lambda? By assuming we define our lambda inside our main function this is how it looks like:

int main::lambda::operator()(const lambda* hidden, const int value)

{

return value + hidden->data;

}

It’s very much similar to our functor other than the name. So now we know that our lambda is just a functor, without a name and with a simplified form.

Another thing that you may notice is the hidden pointer’s name isn’t called this because the this keyword is used for the outer scope’s object.

Callback Function

Both functors and lambdas are often used for writing callback functions. They are very useful when we deal with STL algorithms. For example when we want to transform our data stored in a std::vector. With a functor we can write it as follows:

Functor used with Transform Algorithm— Code by Author

After calling std::transform, we’ll get {2, 3, 4, 5}. With lambda this is how we write it:

Transform Algorithm with Lambda — Code by Author

We can see that it is much neater with a lambda where we can read the code without having to jump to another place to see what operation is done to transform our test_data.

Capturing Variables to create/initialize member variables

We should think about a lambda as an object, to create and initialize member variables we use the capture ‘[]’ mechanism. To create and initialize a variable we can simply write it in ‘[]’:

auto return_one = [value=1](){ return value; };

We can also make a copy of another object in the scope:

void func()

{

int a = 1;

// by value

auto return_one = [value=a](){ return value; };

} void func()

{

int a = 1;

// by reference

auto return_one = [&value=a](){ return value; };

}

Some other details

Some other things that are important to know about lambdas are:

It can be converted to a raw function pointer if it doesn’t capture

Error, non-convertible Lambda — Code by Author

The code above won’t compile because the lambda is not convertible to a function pointer according to the standard, the most obvious reason is that there is a hidden parameter in the operator(). But it is convertible if it doesn’t capture, so the following compiles:

Lambda is convertible to function pointer — Code by Author

This is because there exists a user-defined conversion function for capture-less lambdas.

int(*GetValue)() = [](){return 1;}

By default the overloaded operator() is const

Can’t build — Code by Author

This code won’t compile because we are trying to modify a member variable in a const function. Remember that it looks like this under the hood:

int main::lambda::operator()(const lambda* hidden)

{

return hidden->value++;

}

The hidden pointer points to a constant lambda object hence the error. To modify the captured variable, we add a mutable keyword as follows:

Mutable Lambda — Code by Author

Passing Lambdas as Arguments

We have seen one way to pass a lambda as an argument above, via conversion to a raw function pointer. But that only works for capture-less lambdas.

There are two ways to pass lambdas as arguments of functions:

The STL way, with template

Use the template to pass a lambda — Code by Author

Use std::function

Use std::function to pass a lambda — Code by Author

Summary and References

We have seen that lambda is just a convenient way to write a functor, therefore we should always think about it as a functor when coding in C++.

We should use lambdas where we can improve the readability of and simplify our code such as when writing callback functions.

I hope this post is useful for you because starting from the basics will help us in the long term.

Here are some useful references: