Logo

Blog


Use named lambdas or pass them directly

During the Q&A part of my talk at code::dive Adam Badura asked the question whether passing a lambda directly to a function using it has some performance impact compared to an approach where you create the lambda before the function call and then move it into the function while calling it. Some people then prefer functions with const T& signatures to bind temporaries as well.

My initial response was, that aside from the hard part of coming up with a good name and the possibility to forget the std::move there is no difference. While thinking more about it I think there is more to it.

First, let's check some definitions for lambdas.

wikipedia:

is a function definition that is not bound to an identifier.

and

Anonymous functions can be used for containing functionality that need not be named and possibly for short-term use.

I like these two definitions. Other terms for lambdas are anonymous functions or unnamed functions. Which to some extent states that they don't have a name. However, this is just a definition, it can make sense to name them.

Using a named lambda

Giving the lambda a name can of course increase the readability and make things clear. However, naming is hard. Picking a meaningful name therefor is pretty hard. I personally like it, whenever I can weasel my way around of naming, but this is a personal preference (or field to improve).

If people create a named lambda before the function call and the function in question takes a const T& parameter, the lambda will life and hold all of its captures until it leaves scope:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
template<typename T>
void CallToSomeFunctionTakingALambda(const T&)
{
  // ...
}

void SomeRandomFunction()
{
  auto aCleverlyNamedLambda = [=] { /* capture things */ };

  CallToSomeFunctionTakingALambda(aCleverlyNamedLambda);

  // do some more stuff

  // and even more
}

The mental model is, that CallToSomeFunctionTakingALambda takes a const T& and our aCleverlyNamedLambda captures something expensive for our environment. Let's say a std::string holding a couple of mega bytes of data. Furthermore, after the call to CallToSomeFunctionTakingALambda more code is executed before the function ends. Those the lambda lives for quite a while, still binding the resource. Depending on your environment this can be an issue, as the instance of the std::string now lives longer than it must. Remember, that in other places the advice is often to reduce the scope to a minimum.

Moving a named lambda

The other version would be that CallToSomeFunctionTakingALambda takes a forwarding reference, like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
template<typename T>
void CallToSomeFunctionTakingALambda(T&&)
{
  // ...
}

void SomeRandomFunction()
{
  auto aCleverlyNamedLambda = [=] { /* capture things */ };

  CallToSomeFunctionTakingALambda(std::move(aCleverlyNamedLambda));

  // do some more stuff

  // and even more
}

Due to the std::move we are using, the resources the lambda allocated are free'd after CallToSomeFunctionTakingALambda returns. Thus, there is less pressure on your system. However, in case you forget the call to std::move it behaves the same as before.

Passing an unnamed lambda

Therefore a variant of this could be something like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
template<typename T>
void CallToSomeFunctionTakingALambda(T&&);

void SomeRandomFunction()
{
  CallToSomeFunctionTakingALambda([=] { /* capture things */ });

  // do some more stuff

  // and even more
}

In this version I do not have to pick a name for the lambda and I cannot forget the std::move. Plus, the resources are allocated only for the time needed.

To sum-up

In the first approach, a consumer of the lambda needs to copy it, if it should be stored. This goes away by the second approach, when the lambda is moved into CallToSomeFunctionTakingALambda.

From my perspective the second approach comes with the drawbacks of picking a name and forgetting to call std::move. While the first has the issue that the lambda lives longer than it must.

But I see more now. Aside from considering performance there is consistency. Imaging a code-review situation. In case your guidelines allow passing a lambda to both, either to a const T& or to a T&&, it is hard for a reviewer to judge whether a std::move is missing without knowing the signature of the function the lambda is passed to. Such signatures may change over time. If your coding-guidelines allow only one form, a reviewer can either always point out that a std::move is missing, or can be sure that none is required.

My Conclusion

The longer I thought about it, the more convinced I'm that I would go with number three as a default. Create the lambdas in-place. Just because this way I can drop the need for a good name and with that, arguments over that name. I save them for function or method names, places where I really need good names.

In case, the lambda is used multiple times within a certain function I make it a named lambda. This also implies, that no std::move is required nor allowed. Whenever it comes to a named lambda, check whether it makes more sense to make the lambda a function because there are others requiring the same functionality.

One downside to this approach can be that it inhibits readability, in cases where the body of the lambda does a lot of things. For now, I live with that.

And because a image says more than a thousand words:

C++ Insights.

I know that there are people out there preferring to name the lambda. Feel free to let me know what you think and why you prefer your style.

Andreas