C++ course menu
C++ Lambdas: Syntax, Captures, mutable and std::function
Learn C++ lambda expressions: the [](){} syntax, using lambdas with STL algorithms, capturing variables by value and by reference, [=] and [&], mutable lambdas, generic lambdas with auto, storing lambdas in std::function, and dangling captures.
On this page
A lambda is a small, unnamed function written right where you need it. Lambdas are everywhere in modern C++: as comparison rules for std::sort, conditions for std::count_if, callbacks, and tasks for threads.
Syntax#
[captures](parameters) -> return_type { body }The return type is usually left out; the compiler deduces it from the return statement:
#include <iostream>
int main() {
auto square = [](int x) { return x * x; };
auto greet = []() { std::cout << "Hello from a lambda\n"; };
std::cout << square(7) << '\n';
greet();
}49
Hello from a lambdaauto square = ... stores the lambda in a variable, which you can then call like a function.
Lambdas with algorithms#
Most often, a lambda is passed straight to an algorithm:
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
int main() {
std::vector<std::string> names = {"Meera", "Asha", "Ravindra", "Jo"};
std::sort(names.begin(), names.end(),
[](const std::string& a, const std::string& b) { return a.size() < b.size(); });
for (const auto& n : names) std::cout << n << ' ';
std::cout << '\n';
auto long_names = std::count_if(names.begin(), names.end(),
[](const std::string& n) { return n.size() > 4; });
std::cout << long_names << '\n';
}Jo Asha Meera Ravindra
2Captures: using outside variables#
A lambda cannot use local variables from the surrounding function unless it captures them in the square brackets:
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> marks = {56, 91, 78, 34, 88};
int pass_mark = 60;
auto passed = std::count_if(marks.begin(), marks.end(),
[pass_mark](int m) { return m >= pass_mark; });
std::cout << passed << " passed\n";
}3 passedBy value vs by reference#
A copy is taken when the lambda is created; a reference sees the current value when the lambda runs:
#include <iostream>
int main() {
int rate = 10;
auto by_value = [rate](int x) { return x * rate; };
auto by_ref = [&rate](int x) { return x * rate; };
rate = 20;
std::cout << by_value(5) << ' ' << by_ref(5) << '\n';
}50 100Capturing by reference is how a lambda updates an outside variable:
#include <iostream>
#include <vector>
int main() {
std::vector<int> v = {3, 8, 2};
int total = 0;
for (int x : v) {
[&total](int value) { total += value; }(x); // define and call immediately
}
std::cout << total << '\n';
}13Prefer listing captures explicitly ([pass_mark], [&total]) over [=] and [&]: it makes clear what the lambda depends on.
mutable: changing a captured copy#
Copies captured by value are const inside the lambda. mutable allows changing the lambda's own copy, which persists between calls to that lambda:
#include <iostream>
int main() {
auto counter = [count = 0]() mutable { return ++count; };
std::cout << counter() << ' ' << counter() << ' ' << counter() << '\n';
}1 2 3[count = 0] is an init capture (C++14): it creates a new variable inside the lambda. It is also how you move a value into a lambda: [data = std::move(data)].
Generic lambdas#
Using auto for parameters makes a lambda work with any type (a short way to write a template):
#include <iostream>
#include <string>
int main() {
auto twice = [](const auto& x) { return x + x; };
std::cout << twice(21) << ' ' << twice(1.5) << ' ' << twice(std::string("ab")) << '\n';
}42 3 ababStoring lambdas: std::function#
Every lambda has its own unique type, so to store different lambdas in the same variable or container, use std::function (from <functional>), which can hold any callable with a matching signature:
#include <functional>
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, std::function<double(double, double)>> ops = {
{"add", [](double a, double b) { return a + b; }},
{"sub", [](double a, double b) { return a - b; }},
{"mul", [](double a, double b) { return a * b; }},
};
for (const auto& [name, op] : ops) {
std::cout << name << ": " << op(8, 2) << '\n';
}
}add: 10
mul: 16
sub: 6std::function<double(double, double)> means "a callable that takes two doubles and returns a double". It has a small overhead, so when a function just accepts a callable, a template or auto parameter is faster.
Returning a lambda#
A function can create and return a lambda that remembers a captured value:
#include <iostream>
auto make_multiplier(int factor) {
return [factor](int x) { return x * factor; };
}
int main() {
auto triple = make_multiplier(3);
auto times10 = make_multiplier(10);
std::cout << triple(7) << ' ' << times10(7) << '\n';
}21 70Here factor must be captured by value: a reference to the parameter would dangle once make_multiplier returns.
Common mistakes#
Practice#
Practice exercise
Given std::vector<int> nums = {4, 11, 7, 20, 15, 2}; and a threshold int limit = 10;, use lambdas to: count numbers above limit, double every number in place with std::for_each, and print the result.
Show solution
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> nums = {4, 11, 7, 20, 15, 2};
int limit = 10;
auto above = std::count_if(nums.begin(), nums.end(), [limit](int n) { return n > limit; });
std::cout << above << " above " << limit << '\n';
std::for_each(nums.begin(), nums.end(), [](int& n) { n *= 2; });
for (int n : nums) std::cout << n << ' ';
std::cout << '\n';
}3 above 10
8 22 14 40 30 4Key takeaway#
Finished this lesson?