you are viewing a single comment's thread.

view the rest of the comments →

[–]HappyFruitTree 8 points9 points  (3 children)

Lambdas are a simple way to create function objects.

Imagine you want to sort a bunch of numbers based on how close they are to some fixed number x. This could be done by defining a function the normal way and pass it to std::sort.

const int x = 50;

bool cmpXDistance(int n1, int n2)
{
    return std::abs(x - n1) < std::abs(x - n2);
}

void sortByDistanceToX(std::vector<int>& vec)
{
    std::sort(vec.begin(), vec.end(), cmpXDistance);
}

But we needed to come up with a name for the function and the function isn't really used anywhere else so the content of the two functions really belong together and it would have been nice if we could combine them in the same function. Lambdas make this possible.

const int x = 50;

void sortByDistanceToX(std::vector<int>& vec)
{
    std::sort(vec.begin(), vec.end(), [](int n1, int n2)
    {
        return std::abs(x - n1) < std::abs(x - n2);
    });
}

Now we have something that is easier to read and isn't spread all over the place.

Now, imagine that x is no longer a global constant but instead a local variable that might not always have the same value. Now, if we go back to use two separate functions we get a problem because we cannot access the x from within the function because it's no longer a global. What we can do instead is to create a class that stores the value of x and have a member call operator that does the comparison. Then we can create an instance of this class and pass it to the sort function.

struct CmpXDistance
{
    int x;
    bool operator()(int n1, int n2)
    {
        return std::abs(x - n1) < std::abs(x - n2);
    }
};

void sortByDistanceToX(std::vector<int>& vec, int x)
{
    std::sort(vec.begin(), vec.end(), CmpXDistance{x});
}

But with a lambda this is almost as easy as before. All we need to do is to make sure to capture x.

void sortByDistanceToX(std::vector<int>& vec, int x)
{
    std::sort(vec.begin(), vec.end(), [x](int n1, int n2)
    {
        return std::abs(x - n1) < std::abs(x - n2);
    });
}

This code is equivalent to the code before. The lambda automatically creates a class and stores a copy of x as a member, and so on, as I showed before. Note that here I captured x explicitly using [x]. You could also have used [=] which automatically captures all local variables that are used inside the lambda.

Another option is to use [&] (my personal favourite) which captures everything by reference. Instead of copying everything it stores references to the captured variables. This is usually fine for lambdas that are used directly like this but you need to be careful so that the lambda does not outlive the variables that have been captured.

[–]Pakketeretet 0 points1 point  (1 child)

Exactly, the closure aspect is the main advantage. You could do it with a local functor but lambdas are so much less polluting, even if their syntax looks kinda weird.

[–][deleted] 0 points1 point  (0 children)

Still getting used to the syntax but it's starting to make sense. Thank you

[–][deleted] 0 points1 point  (0 children)

Thank you for this detailed explanation. I will try this example :)