C++ lambdas are anonymous functions which can be defined in another function. They solve the limitation of function where another nested function can’t be defined.

Lambdas are useful when there is a trivial function and is required to be passed around to other functions. For example, in std::find_if, we can provide a lambda predicate function for conditional.

Lambda syntax

Lambda syntax in C++ looks weird as compared to other languages. It looks like below:

[caputreClause] (parameters) -> returnType
{
    statements;
}
  • We can use empty [] if there is no capture clauses (explained in lambda captures).
  • Parameters list can be empty if no parameters are there. It can also be omitted if return type is also omitted.
  • Return type can be omitted. In absence, it will be auto. We avoid using auto as return type for normal functions but as lambdas are small and trivial, we can use auto.

Simple example of lambda

Following shows a simple lambda which does nothing:

[]{};

As there are no parameters and return type, we have omitted the parameter braces.

Following shows an example of std::find_if which finds the element from the array.

std::array items{1, 2, 3, 4, 5, 6};
 
const int *item{std::find_if(items.begin(), items.end(), [](int item)
                                 { return item == 4; })};
 
std::cout << *item << '\n';

What type a lambda is?

While we can directly pass lambda to the function, it is possible to store lambda in a variable as shown below:

auto isEven{
    [](int num) -> bool
    {
        return num % 2 == 0;
    }};

What is the type of the variable isEven? It turns out that lambdas do not have type explicit to us, compiler generates special type for lambdas. However, there are some ways to define the type for the variable. One is already showed using auto, others are:

  1. Using function pointer.
bool (*isOdd)(int){
    [](int num)
    {
        return num % 2 != 0;
    }}; // only when there is no capture for lambda i.e. [] has to be empty.

This method only works when there is no capture clause for lambda.

  1. Using std::function from functionals header file.
 std::function isDivisibleBy3{ // Prior to C++17, need to do std::function<bool (int)>
    [](int num) -> bool
    {
        return num % 3 == 0;
    }};

Most of the time, it is better to use auto method as it correctly gives the type of lambda.

Passing lambdas to function

What type should be used for the parameter that accepts a lambda as argument for a function? Same as above, there are methods to pass lambdas to function as explained below:

  1. Using function templates
template <typename T>
void printByFilter(int *start, int *end, const T &predicate)
{
    while (start != end)
    {
        if (predicate(*start))
        {
            std::cout << *start << '\n';
        }
 
        ++start;
    }
}
 
int main(int argc, char const *argv[])
{
 
    std::array items{1, 2, 3, 4, 5, 6};
 
    printByFilter(items.begin(), items.end(), [](int item)
                  { return item % 2 == 0; });
    return 0;
}

Compiler determines the type T for lambda when the function is called. The only problem with this is that we don’t know what T is and it hides the lambda signature from us.

Above can also be done with abbreviated function template syntax using auto (C++20 and onwards).

void printByFilter(int *start, int *end, const auto &predicate)
{
    while (start != end)
    {
        if (predicate(*start))
        {
            std::cout << *start << '\n';
        }
 
        ++start;
    }
}
  1. Using std::function
void printByMap(int *start, int *end, const std::function<int(int)> &mapper)
{
    while (start != end)
    {
        std::cout << mapper(*start) << '\n';
        ++start;
    }
}
 
printByMap(items.begin(), items.end(), [](int item)
               { return item * item; });

This gives us information about the lambda signature, but it has overhead of implicit conversion of lambda type to std::function. It also has advantage of putting the declaration inside header file and implementation in cpp file.

  1. Using function pointer
int *findIf(int *start, int *end, bool (*predicate)(int))
{
    while (start != end)
    {
        if (predicate(*start))
        {
            return start;
            break;
        }
        ++start;
    }
 
    return nullptr;
}
 
const int *item{std::find_if(items.begin(), items.end(), [](int item)
                                 { return item == 4; })}; // only when there is no capture for lambda i.e. [] has to be empty.
 
std::cout << *item << '\n';

However, this method only works when there is no capture clause for lambda.

Optional return type

If we omit return type from lambda syntax, compiler deduces the return type from the return statements in lambda body. This mandates the return statements to have same return type, otherwise compiler can’t deduce the return type and raise compilation error.

For example:

auto conditionalAdder{
        [](int a, int b, bool isDouble)
        {
            if (isDouble)
            {
                return a + b + 1.0;
            }
            return a + b;
        }};

In this kind of situation, either static cast return statements to same return type or explicitly provide return type in lambda syntax (better approach).

auto conditionalAdder{
        [](int a, int b, bool isDouble) -> double
        {
            if (isDouble)
            {
                return a + b + 1.0;
            }
            return a + b;
        }};

References

  1. https://www.learncpp.com/cpp-tutorial/introduction-to-lambdas-anonymous-functions/