Lambda Expressions allow users to write an inline expression that can be used for short snippets of code in your C++ app which are not going to be reused and don’t require naming. The Lambda Expression construct is introduced in C++ 11 and further developed in the C++17 and C++20 standards. Because of its syntax and definition with [ ]
and ( )
and { }
, sometimes it is hard to understand or remember how it works. In this post, we will explain Lambda Expressions step by step. Here is the first step,
Table of Contents
Step 1: What is a Lambda Expression?
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 help make code cleaner, more concise, and allow you to see behavior inline where it’s defined instead of referring to an external method, like a function.
Lambda Expressions are an expression that returns a function object. It is well explained here. Lambda expressions are widely used in C++, C#, Groovy, Java, Python, Ruby languages too.
The Greek letter Lambda ( λ ) refers to an anonymous function, it means “chosen” since it is equated with something nameless . The “expression” part of the name means it is ‘required’ since the code can be evaluated and will return a value.
A Lambda Expression is assignable to a variable whose data type is usually auto
and defines a function object.
Step 2: What is the syntax of the Lambda Expression?
The syntax for a lambda expression consists of specific punctuation with = [ ]
( )
{ ... }
series.
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.
What is the Simple Syntax of Lambda Expression (C++ 17)?
1 2 3 |
Datatype Lambda Expression = [Capture Clause] (Parameter List) -> Return Type { Body } |
What is the Syntax of Lambda Expression (C++20) version?
1 2 3 |
Datatype Lambda Expression = [Capture Clause] <Template Parameters> (Parameter List) Specifier Exception Attribute -> Return Type { Body } |
Here;
- Datatype is its type like int, float, class, etc. and mostly auto term is used
- Lambda Expression is a definition by the user
- Capture Clause has variables that are visible in the body, capture can happen by value or reference and it can be empty
- Parameter List can be empty or omitted
- Return Type is a data type returned by the body, optional, normally deduced
- Body contains the programming statements to execute and it can be empty
Now lets see each of them.
Step 3: What other parts of Lambda Expression syntax is there?
Datatype
Datatype is the data type of the lambda expression which is its type like int, float
etc. In modern C++, mostly auto
term is used.
Lambda Expression
It is an expression, a definition by the user. It is used like a class name or method name.
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:
- [ ] : Empty Capture. It means that nothing should be captured. Lambda Expression can be written as
auto L= [ ] ( ) { };
- [ = ] : Capture Value in Definition. It specifies that all variables used in the function’s definition should be captured by value. It makes capture by value the default capture type. Lambda Expression can be written as
auto L= [ = ] ( ) { };
- [ & ] : Capture Value in Usage. It specifies that all variables used in the function’s definition should be captured by reference. It makes capture by reference the default capture type. Lambda Expression can be written as
auto L= [ & ] ( ) { };
- [*this] : 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]
- Multiple Capture. 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.
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. - Capture by Copy. 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.
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. It is defined between { and } characters.
Step 4: How do I write a Lambda Expression?
We have added a couple of snippets of lambda expressions using Embarcadero’s C++Builder.
1. [ Captures ] ( Params ) -> ReturnType { 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 9 10 11 12 13 14 15 16 |
#include <iostream> int main() { int pieces; auto total_area = [=] (int a, int b) -> int { return pieces*a*b; }; pieces = 10; std::cout << total_area( 8, 10 ); } |
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 9 10 11 12 13 14 15 |
#include <iostream> int main() { int pieces; auto total_area = [&] (auto a, auto b) { return pieces*a*b; }; pieces = 10; std::cout << total_area( 8, 10 ); } |
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 9 10 11 12 13 14 15 |
#include <iostream> int main() { int pieces; auto total_area = [] { return pieces*8*10; }; pieces = 10; std::cout << total_area(); } |
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.
Step 5: What changes to Lambda Expressions were made in 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.
Step 6: What are some examples of Lambda Expressions?
Now let’s see more syntax examples. We will define a lambda expression to combine datatypes;
1 2 3 |
auto add_things = [](auto a, auto b) { return a + b; }; |
This can be used on any datatypes as given below;
1 2 3 4 5 6 7 8 9 10 11 12 |
auto add_things = [](auto a, auto b) { return a + b; }; // Lambda Expression auto i = add_things(1, 2); std::string hello = "hello"; std::string world = "world"; auto s = add_things(hello, world); std::cout << i << std::endl; std::cout << s << std::endl; |
In a modern way, we can use UnicodeString in C++ Builder VCL or FMX projects. This code will support worldwide languages. Here below we used the Memo component as an output;
1 2 3 4 5 6 7 8 9 10 11 12 |
auto add_things = [](auto a, auto b) { return a + b; }; // Lambda Expression auto i = add_things(1, 2); UnicodeString hello = L"hello"; UnicodeString world = L"world"; auto s = add_things(hello, world); Memo1->Lines->Add(i); Memo1->Lines->Add(s); |
Step 7: Is there a full example of C++ Lambda Expression?
This is a full example about the lambda expression, it has 3 different ways of usage. It is also good to understand auto
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
#include <iostream> #include <tchar.h> auto LExp1 = [ ] (auto a, auto b) -> float { return a + b; }; auto LExp2 = [ ] (auto a, auto b) { return a + b; }; auto LExp3 = [ ] { return 0; }; int _tmain(int argc, _TCHAR* argv[]) { auto L1 = LExp1(3, 5); // L1 is float, because we define return type of Lexp1 is float std::string s1 = "LearnCPlusPlus"; std::string s2 = ".org"; auto L2 = LExp2(s1, s2); // L2 is string, because we didnt define return type, returns by auto datatype auto L3 = LExp3(); // returns 0; } |
Find out more about Lambda Expressions in the Embarcadero DocWiki.
Where can I find out more about using Lambda Expressions in my C++ app?
Here are a couple of other great posts about Lambda Expressions.
C++ Builder is the easiest and fastest C and C++ IDE for building simple or professional applications on the Windows, MacOS, iOS & Android operating systems. It is also easy for beginners to learn with its wide range of samples, tutorials, help files, and LSP support for code. RAD Studio’s C++ Builder version comes with the award-winning VCL framework for high-performance native Windows apps and the powerful FireMonkey (FMX) framework for cross-platform UIs.
There is a free C++ Builder Community Edition for students, beginners, and startups; it can be downloaded from here. For professional developers, there are Professional, Architect, or Enterprise versions of C++ Builder and there is a trial version you can download from here.