Lesson 50
Lambda Expressions (part 2)
Covers lambdas with STL algorithms, generic lambdas, capture-everything syntax, returning lambdas from functions, and recursive lambdas using std::function.
Lesson content
Lambda expressions with STL algorithms
One of the biggest reasons lambda expressions became popular is their seamless integration with the Standard Template Library (STL). Instead of writing separate functions, you can define the required behavior directly where an algorithm is called.
- std::for_each – perform an action on every element
- std::sort – sort a collection
- std::find_if – find the first matching element
- std::count_if – count matching elements
- std::remove_if – remove matching elements
- std::transform – modify elements
This approach keeps related logic in one place and improves readability.
Generic lambdas (C++14)
Before C++14, every parameter type had to be explicitly specified.
[](int a, int b)
{
return a + b;
};With generic lambdas, you can use the auto keyword for parameters. The compiler automatically determines the parameter types, allowing the same lambda to work with integers, floating-point numbers, and even strings.
[](auto a, auto b)
{
return a + b;
};Capturing everything
Instead of listing variables individually, you can capture all local variables.
- [=] – capture everything by value; every variable is copied into the lambda
- [&] – capture everything by reference; changes inside the lambda affect the original variables
- [=, &counter] – mixed capture; everything is copied except counter, which is captured by reference
Returning a lambda
A function can return a lambda expression. This is useful when creating customizable behavior. Each returned lambda remembers its own captured value.
auto createMultiplier(int value)
{
return [value](int number)
{
return value * number;
};
}Recursive lambdas
Recursive lambdas are slightly more advanced because a lambda cannot directly call itself by name. One common approach uses std::function, which allows the lambda to reference itself.
std::function<int(int)> factorial;Example 4: Using a lambda with std::for_each
#include <iostream>
#include <vector>
#include <algorithm>
int main()
{
std::vector<int> numbers =
{
1, 2, 3, 4, 5
};
std::for_each(
numbers.begin(),
numbers.end(),
[](int value)
{
std::cout << value << " ";
}
);
return 0;
}
// Output: 1 2 3 4 5Instead of creating a separate printing function, the lambda performs the task directly inside std::for_each().
Example 5: Sorting with a lambda
#include <iostream>
#include <vector>
#include <algorithm>
int main()
{
std::vector<int> numbers =
{
8, 3, 6, 2, 10, 5
};
std::sort(
numbers.begin(),
numbers.end(),
[](int a, int b)
{
return a > b;
}
);
for (int value : numbers)
{
std::cout << value << " ";
}
return 0;
}
// Output: 10 8 6 5 3 2Normally, std::sort() sorts in ascending order. The lambda changes the comparison rule, resulting in descending order.
Example 6: Finding an element
#include <iostream>
#include <vector>
#include <algorithm>
int main()
{
std::vector<int> numbers =
{
11, 18, 27, 34, 45
};
auto result = std::find_if(
numbers.begin(),
numbers.end(),
[](int value)
{
return value % 2 == 0;
}
);
if (result != numbers.end())
{
std::cout << *result;
}
return 0;
}
// Output: 18Example 7: Generic lambda (C++14)
#include <iostream>
int main()
{
auto add = [](auto a, auto b)
{
return a + b;
};
std::cout << add(10, 20) << std::endl;
std::cout << add(4.5, 2.1) << std::endl;
std::cout << add('A', 5);
return 0;
}
// Output:
// 30
// 6.6
// 70Example 8: Filtering data
#include <iostream>
#include <vector>
int main()
{
std::vector<int> marks =
{
35, 88, 42, 91, 76
};
auto passed = [](int mark)
{
return mark >= 50;
};
for (int mark : marks)
{
if (passed(mark))
{
std::cout << mark << " ";
}
}
return 0;
}
// Output: 88 91 76Real-world uses
- Student grading systems
- Employee filtering
- Product searches
- Banking applications
- Data analytics
- Log processing
Common mistakes and pitfalls
- Capturing nothing [] and trying to use an outside variable. Fix: capture it using [x] or [&x], since outside variables must be explicitly captured
- Capturing by value when updates are needed. Fix: capture by reference, since value capture creates a copy
- Using [&] everywhere. Fix: capture only what is needed, since capturing everything can unintentionally modify variables
- Forgetting mutable when modifying value captures. Fix: add mutable, since captured-by-value variables are read-only by default
- Writing long, complex lambdas. Fix: move complex logic to a named function to improve readability and maintainability
Best practices
- Keep lambda expressions short and focused
- Use lambdas for local behavior, not large business logic
- Capture only the variables you actually need
- Prefer value capture unless the lambda must modify external state
- Use generic lambdas (auto) when parameter types don't matter
- Avoid unnecessary mutable
- Use descriptive variable names when storing lambdas
- Combine lambdas with STL algorithms to write expressive, readable code
- If a lambda grows beyond a few lines, consider replacing it with a named function
When not to use this
- The function is reused in multiple places
- The logic is long or complex
- The function needs extensive documentation
- Multiple developers will maintain the code
- Recursive behavior becomes difficult to understand
- The lambda captures many variables, making dependencies unclear