Home | Projects | Notes > C++ Programming > Lambda Expressions
What is lambda expression?
Motivation
Review of function objects (functors)
Relation between lambdas and function objects
Structure of a lambda expression
Types of lambda expressions
Stateless lambda expression
Stateful lambda expression (capturing context)
Lambdas and the STL
Prior to C++11
Function objects
Function pointers
We often write many short functions that control algorithms
These short functions would be encapsulated in small classes to produce function objects
Many times the classes and functions are far removed from where they are used leading to modification, maintenance, and testing issues
Compiler cannot effectively inline these functions for efficiency.
Function objects
xxxxxxxxxx
141class Multiplier
2{
3private:
4 int num{};
5public:
6 Multiplier(int n) : num{n} {}
7 int operator()(int n) const { return num * n; }
8};
9
10std::vector<int> vec{1, 2, 3, 4};
11Multiplier mult{10};
12
13std::transform(vec.begin(), vec.end(), vec.begin(), mult);
14// vec now contains {10, 20, 30, 40}
Generic function objects
xxxxxxxxxx
201template <typename T>
2struct Displayer
3{
4 void operator() (const T &data) { std::cout << data << " "; }
5};
6
7Displayer<int> d1;
8Displayer<std::string> d2;
9
10d1(100); // d.operator(100);
11 // displays 100
12d2("Jack"); // d.operator("Jack");
13 // displays Jack
14
15std::vector<int> vec1{1, 2, 3, 4, 5};
16std::vector<std::string> vec2{"Jack", "Sunny", "Yena"};
17
18std::for_each(vec1.begin(), vec1.end(), Displayer<int>());
19std::for_each(vec1.begin(), vec1.end(), d1);
20std::for_each(vec2.begin(), vec2.end(), d2);
Using a lambda expression
xxxxxxxxxx
51std::vector<int> vec1{1, 2, 3, 4, 5};
2std::vector<std::string> vec2{"Jack", "Sunny", "Yena"};
3
4std::for_each(vec1.begin(), vec1.end(), [](int x){ std::cout << x << " "; });
5std::for_each(vec2.begin(), vec2.end(), [](std::string s){ std::cout << s << " ";});
The structure of a lambda expression
xxxxxxxxxx
11[] () -> return_type specifiers { };
[]
- Capture list: Defines the start of the lambda (Defines the context in which the lambda executes)
()
- Parameter list: Comma separated list of parameters
return_type
- Return type can be omitted and the compiler will try to deduce it.
specifiers
- Optional specifiers
{}
- Body of your code
A simple lambda expression
xxxxxxxxxx
11[] () { std::cout << "Hi"; };
xxxxxxxxxx
11[] () { std::cout << "Hi"; } (); // Displays Hi
Passing parameters to lambda expressions
xxxxxxxxxx
11[] (int x) { std::cout << x; };
xxxxxxxxxx
11[] (int x, int y) { std::cout << x + y; };
Assigning a lambda expression to a variable
xxxxxxxxxx
21auto l = [] () { std::cout << "Hi"; };
2l(); // Displays Hi
xxxxxxxxxx
31auto l = [] (int x) { std::cout << x; };
2l(10); // Displays 10
3l(100); // Displays 100
Returning a value from a lambda expression
xxxxxxxxxx
61auto l = [] (int x, int y) -> int { return x + y; };
2// Or
3auto l = [] (int x, int y) { return x + y; };
4
5std::cout << l(2, 3); // Displays 5
6std::cout << l(10, 20); // Displays 30
Simple stateless lambda expressions
xxxxxxxxxx
31[] () { std::cout << "Hi"; } (); // Displays Hi
2int x{10};
3[] (int x) { std::cout << x; }(100);
xxxxxxxxxx
121const int n{3};
2int num[n]{10, 20, 30};
3
4auto sum = [] (int nums[], int n)
5{
6 int sum{0};
7 for (int i = 0; i < n; i++)
8 sum += nums[i];
9 return sum;
10};
11
12std:: cout << sum(nums, 3); // Displays 60
Using values and references as lambda parameters
xxxxxxxxxx
21[] (int x) { std::cout << x; };
2[] (int &x) { std::cout << x; };
int x
- Value parameter
int &x)
- Reference parameter
xxxxxxxxxx
131int test_score1{88};
2int test_score2{75};
3
4auto bonus = [] (int &score1, int &score2, int bonus_points)
5{
6 score1 += bonus_points;
7 score2 += bonus_points;
8};
9
10bonus(test_score1, test_score2, 5);
11
12std::cout << "test_score1: " << test_score1 << std::endl; // Displays 93
13std::cout << "text_score2: " << test_score2 << std::endl; // Displays 80
Using pointers as lambda parameters
xxxxxxxxxx
31int x;
2auto l = [] (int *x) { std::cout << *x; };
3l(&x); // '&': Referencing (address-of) operator
xxxxxxxxxx
131int test_score1{88};
2int test_score2{75};
3
4auto bonus = [] (int *score1, int *score2, int bonus_points)
5{
6 *score1 += bonus_points;
7 *score2 += bonus_points;
8};
9
10bonus(&test_score1, &test_score2, 5);
11
12std::cout << "test_score1: " << test_score1 << std::endl; // Displays 93
13std::cout << "text_score2: " << test_score2 << std::endl; // Displays 80
Using arrays and vectors as lambda reference parameters
xxxxxxxxxx
161std::vector<int> test_scores{ 93, 88, 75, 68, 65 };
2
3auto bonus = [] (std::vector<int> &scores, int bonus_points)
4{
5 for (int &score : scores)
6 score += bonus_points;
7};
8
9bonus(test_scores, 5);
10
11std::cout << "test_scores: " << std::endl;
12std::cout << text_scores[0] << std::endl; // Displays 98
13std::cout << text_scores[1] << std::endl; // Displays 93
14std::cout << text_scores[2] << std::endl; // Displays 80
15std::cout << text_scores[3] << std::endl; // Displays 73
16std::cout << text_scores[4] << std::endl; // Displays 70
Using auto
as lambda parameter type specifiers
xxxxxxxxxx
71int num1{10};
2float num2 {20.5};
3
4auto l = [] (auto x) { std::cout << x; };
5
6l(num1);
7l(num2);
xxxxxxxxxx
111std::vector<int> test_scores1 {93, 88, 75, 68, 65 };
2std::vector<float> test_scores2 {88.5, 85.5, 75.5, 68.5, 65.5};
3
4auto bonus = [] (auto &scores, int bonus_points)
5{
6 for (auto &score : scores)
7 score += bonus_points;
8};
9
10bonus(test_scores1, 5); // Valid
11bonus(test_scores2, 5); // Valid
Using lambda expressions as function parameters
xxxxxxxxxx
131// For std::function
2
3void foo(std::function<void(int)> l) { l(10); } // C++14
4// ---- ---
5// return-type parameter-type
6
7// or
8
9void foo(void (*)(int)) { l(10); } // C++14
10
11// or
12
13void foo(auto l) { l(10); } // C++20
Returning lambda expressions from functions
xxxxxxxxxx
111// For std::function
2
3std::function<void(int)> foo() { return [] (int x) { std::cout << x; }; }
4
5// or
6
7void (*foo())(int) { return [] (int x) { std::cout << x; }; }
8
9// or
10
11auto foo() { return [] (int x) { std::cout << x; }; }
All 3 versions are used the same way.
xxxxxxxxxx
21auto l = foo();
2l(10); // Displays 10
Using lambda expressions as function parameters
xxxxxxxxxx
61foo([] (int x) { std::cout << x; });
2
3// or
4
5auto l = [] (int x) { std::cout << x; };
6foo(l);
Using lambda expressions as predicates
xxxxxxxxxx
181void print_if(std::vector<int> nums, bool (*predicate)(int))
2{
3 for (int i : nums)
4 {
5 if (predicate(i))
6 std::cout << i;
7 }
8}
9
10int main()
11{
12 std::vector<int> nums{1, 2, 3};
13
14 print_if(nums, [] (auto x) { return x % 2 == 0; }); // Displays evens
15 print_if(nums, [] (auto x) { return x % 2 != 0; }); // Displays odds
16
17 return 0;
18}
The structure of a stateful lambda expression
xxxxxxxxxx
11[captured_variables] () -> return_type specifiers { };
captured_variables
- Non-empty capture list: Defines what information/variables should be captured.
Compilation of stateless lambda expressions 1
Lambda definition
xxxxxxxxxx
11auto l = [] (int x) { std::cout << x; };
Compiler-generated closure
xxxxxxxxxx
71class CompilerGeneratedName
2{
3public:
4 CompilerGeneratedName();
5
6 void operator() (int x) { std::cout << x; }
7};
Compilation of stateless lambda expressions 2
Lambda definition
xxxxxxxxxx
21int y {10};
2auto l = [y] (int x) { std::cout << x + y; };
Compiler-generated closure
xxxxxxxxxx
91class CompilerGeneratedName
2{
3private:
4 int y;
5public:
6 CompilerGeneratedName(int y) : y{y} { };
7
8 void operator() (int x) const { std::cout << x + y; }
9};
Capture by value
xxxxxxxxxx
21int x{100};
2[x] () { std::cout << x; }(); // Displays 100
Using mutable
to modify variables captured by value
xxxxxxxxxx
91int x{100};
2
3[x] () mutable
4{
5 x += 100;
6 std::cout << x; // Displays 200
7} ();
8
9std::cout << x; // Displays 100
Capture by reference
xxxxxxxxxx
41int x{100};
2
3[&x] () { x += 100; }();
4std::cout << x; // Displays 200
Capture by value and reference
xxxxxxxxxx
41[x, y] // Capture both x and y by value
2[x, &y] // Capture x by value and y by reference
3[&x, y] // Capture x by reference and y by value
4[&x, &y] // Capture both x and y by reference
Default captures
xxxxxxxxxx
31[=] // Default capture by value
2[&] // Default capture by reference
3[this] // Default capture this object by reference
Using default and explicit captures
xxxxxxxxxx
31[=, &x] // Default capture by value but capture x by reference
2[&, y] // Default capture by reference but capture y by value
3[this, z] // Default capture this by but capture z by value