Lambda Functions
Lambda functions were introduced by the C++ - 11 standard, allowing to write anonymous functions that can be used instead of complete structs or classes, avoiding the need to create complete class and function definitions.
As an example, consider a function to compute the square of a number. In C++ - 11, this can be done with a lambda function as follows:
auto squareLambdaFunction = [](int n) {return n * n; };
int nineSquared = squareLambdaFunction(9);
After the auto keyword, to let C++ decide on the return type, and naming our function as we desire, the square brackets indicate that what follows is intented to be a lambda function. Inside the parentheses, as always, we can define a list of arguments to be received by the function, which can be omitted altogether if there are no arguments, meaning that we do not need to write () if there is no input, unlike for regular functions. Finally there is the regular body of code inside the braces, just as for normal functions.
Capture Semantics
Let us try a more difficult example. Assume that there is a list of strings and we now want to create another list with the sizes of those strings using a lambda function to evaluate the size of each string and pushing it on the new list:
std::wstring test1 = L"bell0bytes";
std::wstring test2 = L"Symplectic Topology is the best!";
std::wstring test3 = L"Deutscher Meister wird nur der BVB!";
std::vector<std::wstring> stringVector;
stringVector.push_back(test1);
stringVector.push_back(test2);
stringVector.push_back(test3);
The obvious old C++ way would be do populate the new vector as follows:
std::vector<size_t> stringSizeVector;
for (auto s : stringVector)
stringSizeVector.push_back(s.size());
Let us try to do that with lambda functions. Obviously our lambda function will get a string as input, and it has to return the size of the string:
std::vector<size_t> stringSizeVector;
std::for_each(stringVector.begin(), stringVector.end(), [](std::wstring s) { stringSizeVector.push_back(s.size()); });
If we try this, the compiler complains: error C3493: 'stringSizeVector' cannot be implicitly captured because no default capture mode has been specified.
Thus, there is one more detail to talk about: how do we pass parent objects by reference? Well, the square brackets indicate how the variables are captured:
- []: no capture.
- [&]: capture all by reference.
- [=]: capture all by value.
Capturing variables by value will naturally prevent them from being modified within the function. Not capturing them prevents them from even being accessed. Let us try the above code again. Using capture by value obviously won't work, as we have to push something on the vector, thus we have to use capture by reference:
std::vector<size_t> stringSizeVector;
std::for_each(stringVector.begin(), stringVector.end(), [&](std::wstring s) { stringSizeVector.push_back(s.size()); });
In addition, one can specify lists of variables to capture. If a variable is preceded by a &, it is captured by reference, otherwise it is captured by value:
int foo, bar, lol;
[&foo, bar, &lol](int i) {foo = i; lol += bar; };
Here, foo and lol are captured by reference, while bar is captured by value. No other variables from the parent scope are accessible at all.
To access all members of an enclosing class, the this keyword can be used. Note that this technique also works when passing a lambda function as an argument to a function in a different: the captured class will be the one the lambda function was originally defined in.
As a last technique, one can also specify that all variables should be captured by value or by reference, minus a few exceptions:
int foo, bar, lol;
[&, bar](int i) {foo = i; lol += bar; };
Generalized Lambda Functions
The C++ - 14 standard introduced the concept of generic lambda functions, which means that we are now allowed to use auto as the type for input parameters. We could now write our squaring function as follows:
auto squareLambdaFunction = [](auto n) {return n * n; };
Our new generalized lambda function can now be used for integers as well as for floats and doubles, for example, without recurring to templates!
This is quite the powerful technique when combined with standard containers. As an example, let us sort and print the content of a vector using generic lambda functions:
std::vector<int> vectorOfIntegers;
for (unsigned int i = 0; i < 100; i++)
vectorOfIntegers.push_back(std::rand() % 1000);
std::sort(vectorOfIntegers.begin(), vectorOfIntegers.end(), [](auto n, auto m) {return n > m; });
std::for_each(vectorOfIntegers.begin(), vectorOfIntegers.end(), [](auto n) {std::cout << n; });
Okay, to be honest, there is an even easier way to do this, using a predefined function from C++ - 14, but I am sure you can see why lambda functions can be a very useful and powerful tool to have in your arsenal:
std::vector<int> vectorOfIntegers;
for (unsigned int i = 0; i < 100; i++)
vectorOfIntegers.push_back(std::rand() % 1000);
std::sort(vectorOfIntegers.begin(), vectorOfIntegers.end(), std::greater<>());
std::for_each(vectorOfIntegers.begin(), vectorOfIntegers.end(), [](auto n) {std::cout << n; });
As a small example how to use lambda functions in classes, imagine programming a game. Now a game menu usually has a few buttons, for example one of the buttons, the "Play Game" button starts the game when pressed:
namespace UI
{
class Button
{
private:
std::wstring name; // the name of the button
public:
Button(std::wstring name) : name(name) { };
~Button() {};
std::function<void()> onClick;
};
}
The Button class has a function which takes no input and returns nothing.
Now in the main menu, one would simply create this button, and define its onClick function with a lambda function as follows:
UI::Button button(L"Play Button");
button.onClick = []()
{
// change scene from main menu to the actual game scene
};
Return Type
Last, but not least, it is also possible to specify the return value of lambda functions, by using the "->" keyword. Here is an example from my game project:
auto onClickSave = [this]() -> util::Expected<bool>
{
// store the new key binding
dxApp->saveKeyBindings();
if (!dxApp->popGameState().wasSuccessful())
return std::runtime_error("Critical error: Unable to pop new key binding state!");
return false; // notify stack change
};
To see a practical example of the above techniques, check out the following tutorial on how to implement a game menu with buttons using lambda functions.