A lambda expression defines an anonymous function or a closure at the point where it is used. You can think of a lambda expression as an unnamed function (that’s why it’s called “anonymous”). Lambda expressions make code cleaner and more concise, and allow you to see behavior inline where it’s defined instead of referring to an external method, like a functor. They usually follow the following format:
1 2 3 4 |
[ capture clause ] (optional parameter list) -> optional return-type { body of function } |
Lambda expressions are one example of modern C++ language features. The aim of this article is to provide information about lambda expressions, parts of the expressions, and how they can be used while writing code.
Table of Contents
Parts of a lambda expression
The parts of a lambda expression include a capture clause, an optional parameter list, an optional return type, and the function’s body (although some parts are not required).
Capture Clause
Capture clauses are used to instruct the compiler to capture the variables visible in scope around where the lambda is declared, either by copy or by reference, to be available for use inside the lambda method body. Capture clauses can be used in different ways to specify the requirements of functions.
The capture clause is the first part of a lambda, in square brackets []
.
Here are a few ways to declare capture clauses:
[](){}
means that nothing should be captured.[=](){}
specifies that all variables used in the function’s definition should be captured by value. It makes capture by value the default capture type.[&](){}
specifies that all variables used in the function’s definition should be captured by reference. It makes capture by reference the default capture type.- Capturing *this was introduced in C++11. Its purpose is to either capture the copy of the current object, or capture the object itself. Capturing
[*this]
by value was introduced in C++17. Capturing [*this] by value is important in a scenario where a lambda expression is asynchronously dispatched from a non-static member function. The pointer might not be valid when the function runs. For that reason, it is important to be able to capture *this by value. You can read more about the new feature here: Lambda Capture of *this by Value as[=,*this]
- Both
[=]
and[&]
can be used together in one capture clause. For example,[=, &var]
specifies that all variables should be captured by copy but variable “var
” should be captured by reference. - Similarly, you can capture all variables by reference and specify particular variables to capture by copy. For example,
[&, var]
specifies that all variables should be captured by reference but variable “var
” should be captured by copy. - Some capture clause definitions result in errors due to redundancy. For example, a capture clause like
[&, &var]
is problematic since we already specified that all variables should be captured by reference and then re-specified that var should be captured by reference.
Parameters
The parameter list of a lambda is just like the parameter list of any other method: a set of parameters inside braces ()
. Input parameters in a lambda expression can be listed while defining lambda expressions but are not required.
Return Type
Often the return type does not need to be specified because it can be deduced by the compiler. If you have a single return statement, the compiler can easily deduce the return type. For more complex lambdas you may need or choose to specify it explicitly.
Body
The body of a lambda expression simply contains the code, the same as a normal function.
Different ways of writing lambda expressions
We have added a couple of snippets of lambda expressions using Embarcadero’s C++Builder.
1. [ captures ] ( params ) -> ret { body }
In this method, we are using a capture clause, specifying some parameters, as well as the return type.
Sample code:
1 2 3 4 5 6 7 8 |
#include <iostream> int main() { int value = 6; auto myFunction = [=] (int a) -> int {return value * a * 4;}; std::cout << myFunction(4); } |
What this code means is that all variables should be available copied by value and the return type of the function is int. The statements enclosed within the curly braces represent the body of the function: note that the method both uses a variable outside the lambda (value, captured by copy, so available as a const value) and a normal parameter (a)
For this lambda the compiler could deduce what the return type is, but we’ve specified it anyway as int.
2. [ captures ] ( params ) { body }
In this method, we are using a capture clause, and specifying some parameters. The return type should be deduced.
Sample code:
1 2 3 4 5 6 7 8 |
#include <iostream> int main() { int value = 6; auto myFunction = [&] (int a) {return value * a;}; std::cout << myFunction(4); } |
What this means is that all variables should be available by reference (meaning they can be written back to) and the return type of the function should be deduced. The statements enclosed within the curly braces represent the body of the function.
Normally, captured variables are const value variables. They are copies and cannot be modified. Here, since value is captured by reference, you can modify the value of “value” from within the lambda.
3. [ captures ] { body }
In this method, we are using a capture clause, and have excluded parameters. The return type should be deduced.
Sample code:
1 2 3 4 5 6 7 8 |
#include <iostream> int main() { int value = 6; auto myFunction = [=] {return value * 2;}; std::cout << myFunction(); } |
Lambda expressions can also be written by only specifying the capture clause and defining the body of the function. When the return type is not specified, it will be deduced.
Changes made to C++17
Updates to several features were made in C++17. Some of the changes made to lambda expressions in C++17 include:
- Introduction of
Constexpr
Lambda Expressions: Lambda expressions can now be declared as constexpr. The constexpr keyword can be used to specify that an expression should be executed at compile time. Note that the lambda expression will need to follow certain rules if it is declared as constexpr. If an expression is declared as constexpr, the expression’s body should not contain code that is not constexpr. For example, memory should not be allocated dynamically.
- Capture of
[*this]
in lambda expressions: Capture of[*this]
by value in lambda expressions was introduced in C++17. Prior to C++17, this functionality was not allowed and so one would need to capture it by creating a copy (which is prone to errors).
In conclusion, lambda expressions provide a neat and concise alternative to writing functions. They can be declared in different ways based on users’ requirements by making use of capture clauses and parameters (which are optional). Additionally, return types can be deduced and lambda expressions can be declared as constexpr.
Design. Code. Compile. Deploy.
Start Free Trial Upgrade Today
Free Delphi Community Edition Free C++Builder Community Edition
You may have dropped your “int” by accident for #1. It should be this, instead:
auto myFunction = [=] (int a) -> int {return value * a * 4;};
thank you for catching that