[Syntax]
[Wrapping a lambda in a lambda]
[Use std::function as a polymorphic wrapper]
[Concatenate lambdas with recursion]
[Combine predicates with logical conjunction]
[Call multiple lambdas with same input]
[Use mapped lambdas for a jump table]
[Pass copy of object to lambda]
[Improvements for several C++ versions]
A lambda is an anonymous function. The following basic example shows a lambda without params and a second lambda which expects as param a lambda (or function) needing no params:
const char* helloTxt = "Hello\n";
auto greeting = [helloTxt] {return helloTxt; };
auto write = [](auto genMsg) {std::cout << genMsg(); };
write(greeting);
write([] {return "Hi from anonymous lambda without name\n"; });
Output:
Hello
Hi from anonymous lambda without name
In the example above lambda greeting
needs access to the variable helloTxt
and gets access by defining the variable as a capture within square brackets. The ability to capture variables outside its own scope is what makes a lambda a closure (= function that allows the use of symbols outside its own lexical scope).
Syntax
You can define lambda greeting
with more details:
auto greeting = []()-> const char * {return helloTxt;};
In most cases the compiler can determine the return type via automatic type deduction and you can omit the trailing return type syntax with “->
someType
“.
A minimum lambda which captures nothing, has no params and does nothing is given by:
[]{}
More on captures
If you want to change some outside variable, you have to capture it by reference:
int someCounter{ 42 };
auto increment = [&someCounter] {std::cout << std::format("{}\n", ++someCounter); };
increment();
increment();
std::cout << std::format("{} (within outer scope)\n", someCounter);
Output:
43
44
44 (within outer scope)
With [=] you capture all outside variables as copies. With [&] you capture all outside variables as references. To be able to access class members you have to capture this
or *this
.
With attribute mutable you can change even variables captured as copies within the lambda. But within outer scope no changes occur:
int otherCounter{ 42 };
auto increment2 = [otherCounter] () mutable {std::cout << std::format("{}\n", ++otherCounter); };
increment2();
increment2();
std::cout << std::format("{} (within outer scope)\n", otherCounter);
Output:
43
44
42 (within outer scope)
You can also define a local capture variable that maintains its state. Here mutable must also be specified otherwise a change would not be possible:
auto increment3 = [localCounter = 42]() mutable {std::cout << std::format("{}\n", ++localCounter); };
increment3();
increment3();
Output:
43
44
Wrapping a lambda in a lambda
You can parametrize a function / lambda to return a specific lambda for the given parameter:
const std::vector<int> v{1, 7, 4, 9, 4, 8, 12, 10, 20};
std::string foundNumbers;
// Wrapping the lambda into an other lambda
auto isDivBy = [&foundNumbers](int divisor)
{
return [&foundNumbers, divisor](int i) {
if (i % divisor == 0)
{
foundNumbers += std::format("{} ", i);
return true;
};
return false; };
};
for (int divisor : {3, 4, 5})
{
foundNumbers.clear();
// Get the predicate / lambda function for the required divisor
auto predicate = isDivBy(divisor);
auto count = std::ranges::count_if(v, predicate);
std::cout << std::format("{} numbers dividable by {}: {}\n", count, divisor, foundNumbers);
}
Output:
2 numbers dividable by 3: 9 12
5 numbers dividable by 4: 4 4 8 12 20
2 numbers dividable by 5: 10 20
Use std::function as a polymorphic wrapper
You can store different specializations of a lambda within a vector using std::function.
The following example has 3 lambdas each storing values in a container of specific type:
std::deque<int> d;
std::list<int> l;
std::vector<int> v;
// a lambda to get a container specific anonymous lambda for storing
auto storeInContainerFunc = [](auto& container)
{
return [&container](auto value) {container.push_back(value); };
};
const std::vector<std::function<void(int)>>
storeFunctions {storeInContainerFunc(d), storeInContainerFunc(l), storeInContainerFunc(v)};
for (auto& store : storeFunctions) // iterate over specific containers
for (int i{ 0 }; i < 10; ++i)
store(i);
PrintContainer(d, "d");
PrintContainer(l, "l");
PrintContainer(v, "v");
Output:
d: 0 1 2 3 4 5 6 7 8 9
l: 0 1 2 3 4 5 6 7 8 9
v: 0 1 2 3 4 5 6 7 8 9
Concatenate lambdas with recursion
You can generically combine lambdas to a single lambda so that the output of one is the input of the next:
template <typename T, typename ...Ts>
auto concat(T t, Ts ...ts)
{
if constexpr (sizeof...(ts) > 0) // number of elements in the parameter pack
{
return [&](auto ...parameters)
{
// recursively call concat again without first lambda t
return t(concat(ts...)(parameters...));
};
}
else
{
return t;
}
}
The following example combines 3 calculating lamdas/funcs to a single lambda. Precondition for proper concatenation is, that the return value type of the preceding lambda matches to the required input value types of the next lambda:
auto multiplyBy2 = [](auto i) {return i * 2; };
auto multiplyBy3 = [](auto i) {return i * 3; };
auto combinedCalc = concat(multiplyBy3, multiplyBy2, std::plus<int>{});
// Calculation starts with std::plus which has two arguments
std::cout << std::format("Calc result = {}", combinedCalc(2, 3));
Output:
Calc result = 30
Combine predicates with logical conjunction
Build a needed predicate function to work with a std algorithm (std::copy_if). The predicate function is built by a logical combination of two lambdas both accepting the same param type:
template <typename F, typename A, typename B>
auto combine (F binaryFunc, A a, B b)
{
return [=](auto param) { return binaryFunc(a(param), b(param)); };
}
The example uses logical AND for combination:
auto beginsWith_a = [](const std::string& s) {return s.find("a") == 0; };
auto endsWith_b = [](const std::string& s) {return s.rfind("b") == s.length() - 1; };
auto bool_and = [](const auto& l, const auto& r) {return l && r; };
// Find matching strings from std::cin and write them to std::cout
std::copy_if(std::istream_iterator<std::string>{std::cin}, {},
std::ostream_iterator<std::string>{std::cout, " "},
combine(bool_and, beginsWith_a, endsWith_b));
std::cout << '\n';
Command window:
>echo aabb bbaa foo abzb | MyTestApp.exe
aabb abzb
Call multiple lambdas with same input
You can create multiple instances of a lambda with different capture values with use of a generating lambda function. The example displays several integers enclosed in all types of braces. For each brace type a separate lambda version is used:
auto braces = [](const char a, const char b)
{
return [a, b](int param) {std::cout << std::format("{}{}{} ", a, param, b); };
};
// Get different lambdas for each specific brace type
auto a = braces('(', ')');
auto b = braces('[', ']');
auto c = braces('{', '}');
auto d = braces('|', '|');
for (int i : {1, 2, 3, 4, 5})
{
for (auto writeWithSpecificBrace : { a, b, c, d }) writeWithSpecificBrace(i);
std::cout << '\n';
}
Output:
(1) [1] {1} |1|
(2) [2] {2} |2|
(3) [3] {3} |3|
(4) [4] {4} |4|
(5) [5] {5} |5|
Use mapped lambdas for a jump table
To select a corrresponding action depending on some (user) input a jump table can be used. In the following example the jump table is realized by storing appropriate lambda functions within a map. The key of the map is the single allowed input character:
Helper function to get single allowed charcter from user input
const char PromptForSingleChar(const char* p)
{
std::string r;
std::cout << std::format("{} > ", p);
std::getline(std::cin, r, '\n');
if (r.size() < 1) return '\0';
if (r.size() > 1) return '\0';
return std::toupper(r[0]);
}
Main code with jump table:
using JumpFunc = void(*)();
std::map<const char, JumpFunc> jumpMap {
{ 'A', [] {std::cout << "func A\n"; } },
{ 'B', [] {std::cout << "func B\n"; } },
{ 'C', [] {std::cout << "func C\n"; } },
{ 'D', [] {std::cout << "func D\n"; } },
{ 'X', [] {std::cout << "Bye!\n"; } }
};
char selChar{};
while (selChar != 'X')
{
if (selChar = PromptForSingleChar("Enter A/B/C/D/X"))
{
auto it = jumpMap.find(selChar);
if (it != jumpMap.end())
{
// call stored lambda
it->second();
}
else
std::cout << "Invalid response\n";
}
}
Command window:
Enter A/B/C/D/X > c
func C
Enter A/B/C/D/X > D
func D
Enter A/B/C/D/X > f
Invalid response
Enter A/B/C/D/X > x
Bye!
Pass copy of object to lambda
Instead of simply passing “this” to the lambda function it is also possible to pass a copy of the current object instance. Motivation: The lambda may be executed later when the internal state of the object already has changed or when the object has been destroyed.
class SomeClass
{
public:
int m_data;
auto DoSomethingAsnycLater()
{
return std::thread([*this]{
// wait for something or some long enduring action
// then access member data (of copied instance)
m_data += ... ;});
}
};
std::thread t;
{
SomeClass myInstance;
t = myInstance.DoSomethingLater();
}
t.join(); // wait here until thread function has processed on the copy of myInstance
Improvements for several C++ versions
- typed lambda
auto sumInt = [](int x, int y) {return x+y;)
C++11, accepts only types convertible to int - generic lambda
auto sumGen = [](auto x, auto y) {return x+y;)
C++14, accepts all types - generic lambda + decltype
auto sumDec = [](auto x, decltype(x) y) {return x+y;)
C++14, second type must be convertible to the first type
Example:sunDec(true, 2000) = 2
(second param is converted to bool (true), when adding bools they are promoted to int which causes true+true = 1+1 = 2 - template lambda
auto sumTem = []<typename T>(T x, T, y) {return x+y;}
C++20, first and second parameter must have the same type
Template lambda with container types
auto lambdaGen = [](const auto& container) {return container.size();}
supports any type with member function “size()”- auto lambdaVec = []<typename T>(const std::vecor<T>& vec) {return vec.size();}
supports only std::vector of any type (int, double,…) auto lambdaVecInt= []<std::integral T>(const std::vecor<T>& vec) {..}
supports only std::vector of integral type (int, long,..)