Lambda expression C# là gì

A lambda expression provides a concise way to create simple function objects. A lambda expression is a prvalue whose result object is called closure object, which behaves like a function object.

The name 'lambda expression' originates from lambda calculus, which is a mathematical formalism invented in the 1930s by Alonzo Church to investigate questions about logic and computability. Lambda calculus formed the basis of LISP, a functional programming language. Compared to lambda calculus and LISP, C++ lambda expressions share the properties of being unnamed, and to capture variables from the surrounding context, but they lack the ability to operate on and return functions.

A lambda expression is often used as an argument to functions that take a callable object. That can be simpler than creating a named function, which would be only used when passed as the argument. In such cases, lambda expressions are generally preferred because they allow defining the function objects inline.

A lambda consists typically of three parts: a capture list [], an optional parameter list () and a body {}, all of which can be empty:

[](){} // An empty lambda, which does and returns nothing

Capture list

[] is the capture list. By default, variables of the enclosing scope cannot be accessed by a lambda. Capturing a variable makes it accessible inside the lambda, either as a copy or as a reference. Captured variables become a part of the lambda; in contrast to function arguments, they do not have to be passed when calling the lambda.

int a = 0; // Define an integer variable auto f = []() { return a*9; }; // Error: 'a' cannot be accessed auto f = [a]() { return a*9; }; // OK, 'a' is "captured" by value auto f = [&a]() { return a++; }; // OK, 'a' is "captured" by reference // Note: It is the responsibility of the programmer // to ensure that a is not destroyed before the // lambda is called. auto b = f(); // Call the lambda function. a is taken from the capture list and not passed here.

Parameter list

() is the parameter list, which is almost the same as in regular functions. If the lambda takes no arguments, these parentheses can be omitted (except if you need to declare the lambda mutable). These two lambdas are equivalent:

auto call_foo = [x](){ x.foo(); }; auto call_foo2 = [x]{ x.foo(); };

C++14

The parameter list can use the placeholder type auto instead of actual types. By doing so, this argument behaves like a template parameter of a function template. Following lambdas are equivalent when you want to sort a vector in generic code:

auto sort_cpp11 = [](std::vector::const_reference lhs, std::vector::const_reference rhs) { return lhs < rhs; }; auto sort_cpp14 = [](const auto &lhs, const auto &rhs) { return lhs < rhs; };

Function body

{} is the body, which is the same as in regular functions.

Calling a lambda

A lambda expression's result object is a closure, which can be called using the operator() (as with other function objects):

int multiplier = 5; auto timesFive = [multiplier](int a) { return a * multiplier; }; std::out << timesFive(2); // Prints 10 multiplier = 15; std::out << timesFive(2); // Still prints 2*5 == 10

Return Type

By default, the return type of a lambda expression is deduced.

[](){ return true; };

In this case the return type is bool.

You can also manually specify the return type using the following syntax:

[]() -> bool { return true; };

Mutable Lambda

Objects captured by value in the lambda are by default immutable. This is because the operator() of the generated closure object is const by default.

auto func = [c = 0](){++c; std::cout << c;}; // fails to compile because ++c // tries to mutate the state of // the lambda.

Modifying can be allowed by using the keyword mutable, which make the closer object's operator() non-const:

auto func = [c = 0]() mutable {++c; std::cout << c;};

If used together with the return type, mutable comes before it.

auto func = [c = 0]() mutable -> int {++c; std::cout << c; return c;};

An example to illustrate the usefulness of lambdas

Before C++11:

C++11

// Generic functor used for comparison struct islessthan { islessthan(int threshold) : _threshold(threshold) {} bool operator()(int value) const { return value < _threshold; } private: int _threshold; }; // Declare a vector const int arr[] = { 1, 2, 3, 4, 5 }; std::vector vec(arr, arr+5); // Find a number that's less than a given input (assume this would have been function input) int threshold = 10; std::vector::iterator it = std::find_if(vec.begin(), vec.end(), islessthan(threshold));

Since C++11:

C++11

// Declare a vector std::vector vec{ 1, 2, 3, 4, 5 }; // Find a number that's less than a given input (assume this would have been function input) int threshold = 10; auto it = std::find_if(vec.begin(), vec.end(), [threshold](int value) { return value < threshold; });


Lambda expression C# là gì
PDF - Download C++ for free

Lambda expression C# là gì
Photo by Tudor Baciu on Unsplash

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 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:

whereas a functor is as follows:

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:

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

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 ‘[]’:

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

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.

  • 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:

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 AuthorUse 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: