Home | Projects | Notes > C++ Programming > Stateless Lambda Expressions
Stateless lambda expressions are lambdas that do not capture any external variables from their surrounding scope. An empty capture list means that the expression captures no information from its environment and only has access to the data passed through its function parameter list.
31[] () { std::cout << "Hi"; } (); // Displays Hi
2int x{10};
3[] (int x) { std::cout << x; }(100);
L3: This lambda is invoked with the value
100
passed to itsx
parameter. Since it is stateless, it captures no information from its surrounding environment. This means it has no access to thex
variable defined earlier and only operates on the value explicitly passed to its parameter when it is called.
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
L4: From the empty capture list, we know that this lambda is stateless, meaning it has no access to the array or its length defined earlier. The only way it can compute the sum of the integers in the array is if both the array and its length are passed as parameters. This is exactly what happens when the lambda is called in L12.
21[] (int x) { std::cout << x; };
2[] (int &x) { std::cout << x; };
L1:
int x
- Value parameterL2:
int &x)
- Reference parameter (i.e., alias to the actual parameter; no copy is made)
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
31int x;
2auto l = [] (int *x) { std::cout << *x; }; // '*': dereferencing operator
3l(&x); // '&': Referencing (address-of) operator
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
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
auto
as Lambda Parameter Type SpecifiersThis allows the lambda expression to accommodate different types of arguments, making it more flexible and enabling it to work like a generic function. (The auto
keyword is the key!)
Note: auto
is not an actual type. It's an instruction telling the compiler to deduce the actual type.
71int num1{10};
2float num2 {20.5};
3
4auto l = [] (auto x) { std::cout << x; };
5
6l(num1);
7l(num2);
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
61// For std::function
2
3void foo(std::function<void(int)> l) { l(10); } // C++14
4void foo(void (*)(int)) { l(10); } // C++14
5
6void foo(auto l) { l(10); } // C++20
L3: Passing a lambda expression to a function as a function object using the standard library's
<functional>
header. The functionfoo
takes the function objectl
as a function parameter.void
type specifier represents the return type of the function object, and theint
type specifier represents the function object's parameter type. (C++14)L4: Passing a lambda expression to a function as a function pointer. The function
foo
takes as its parameter a pointer to the functionl
. (C++14)L6: In C++20, we can eliminate the need to explicitly declare return and parameter types by using the
auto
keyword, allowing the compiler to deduce both the parameter types and the return type of the lambda expression.
Similar to how the lambda expressions are passed to functions, they can be returned as either function objects, function pointers or by using the auto
keyword to instruct the compiler to deduce the return type.
61// For std::function
2
3std::function<void(int)> foo() { return [] (int x) { std::cout << x; }; }
4void (*foo())(int) { return [] (int x) { std::cout << x; }; }
5
6auto foo() { return [] (int x) { std::cout << x; }; }
L3: Returning a lambda expression as a function object.
L4: Returning a lambda expression as a function pointer. This is an old-style C syntax that has persisted for backward compatibility. In modern C++, it's uncommon to use this approach to return a lambda expression from a function. Instead, it's more typical—and more flexible—to return lambdas either as function objects or by using the
auto
keyword.
All 3 versions are used the same way:
21auto l = foo();
2l(10); // Displays 10
Examples of why you might want to return a lambda from a function are best illustrated using stateful lambda expressions, which will be discussed in the next section.
41foo([] (int x) { std::cout << x; });
2
3auto l = [] (int x) { std::cout << x; };
4foo(l);
L1: A common way of passing lambda expressions to functions since in most cases they're only ever passed once.
L3: If the lambda will be used more than once, it may be beneficial to assign it to a variable so that it can be passed to multiple functions and called independently without having to define the lambda each time.
A predicate in C++ is a function that takes one or more arguments and returns a boolean value. Naturally, a predicate lambda is a lambda expression that implements this behavior. This is where the true power of lambdas shines—enabling concise, inline logic for filtering, searching, and decision-making in algorithms.
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}
L1: Takes as its parameters an integer vector and a predicate lambda that's passed as a function pointer. In this case, the predicate lambda is used to determine which elements of the integer vector to display.
Predicate lambdas are especially important when working with Standard Template Library (STL) functions and algorithms such as std::sort()
or std::for_each()
, which often take a predicate as a parameter to customize their behavior.