1

This article declares using C++ Lambdas to be "cheap": Lambdas aren't magic - part 2

They demonstrate how to pass lambdas to existing std functions / templates. One article demonstrates how to use "auto" as return type of a function to return a lambda without using std::function.

No article I saw demonstrates making your own functions, esp. class member functions, which take a lambda or more, without using std::function.

So this bold declaration of "lambdas are cheap" - is it really true, in real world scenarios?

As a reference: "Cheap" is to me in this particular quest to sort this out: Sensibly usable on embedded bare metal projects with few hundreds kilobytes of memory and double digit MHz speeds. (I have been using a sane subset of C++ in that realm and am looking for what else I can use)

From what I saw, std::function<> is not cheap. Lambdas passed as a std::function apparently can't get inline optimized anymore, for one thing. But worse, a std::function<> is 32 bytes big. Also apparently, if more than fits in there is captured, dynamic allocation might be used? That all sounds like bad news.

So while I was looking for ways to use lambdas without std::function, and only found one example returning auto, I tried this: I have made a very simple class that uses "auto" for argument types in member functions, and the compiler seemed happy with it (though it's not as "self documenting" code as with std::function with regards to expected functor arguments).

struct FuncyClass
{   unsigned func(auto fnx)
    {   return 2 * fnx(7);
    }
};

int main()
{   FuncyClass fc;
    auto result = fc.func( [](auto x){return x*3;} );
    
    printf("Result: %u\n", result);
    return 0;
}
// Output: "Result: 42"

But I have a feeling that this very simple scenario is not showing me possible compiler error floods to come when this gets used by more complicated scenarios. I do not deeply understand what is going on behind the scenes with this syntax, what the compiler does when determining, from the usage of the functor, what arguments and return type are expected, and how this function with auto arguments is implemented under the hood.

Is that, i.e. using auto typed args, really a sensible way of making your class member functions lambda-customizable? Then it would seem "cheap", as at leats in my test the executable was considerably smaller when using auto instead of std::function.

It's still limtied, of course: There is no way for a class to hold on to a lambda without using std::function<> (or a DIY wrapper of similar sort), right? Would it be possible to prevent dynamic allocations from ever happening, e.g. making it a compile-time error when a scenario arises that would require std::function to allocate memory?

6
  • 8
    Lambda itself is cheap. std::function is not, but you are not required to use it. Non-capturing lambdas are convertible to function pointers, capturing lambdas can be passed as templates. Commented Aug 26, 2020 at 12:21
  • std::function is expensive because it uses type erasure. A lambda is just a short hand functor and those are "cheap" in comparison. You can make your class a template and give it the lambda's type to avoid using std::function. Commented Aug 26, 2020 at 12:22
  • "not required to use std::function" - yeah, but that's unfortunately the "way to do it"as is shown in probably 10 articles / blog posts I read about this - so my impression was it is the way to usefully employ lambdas in your own classes.
    – sktpin
    Commented Aug 26, 2020 at 12:25
  • 7
    Pretty much all standard library functions that take a function/functor parameter (e.g. std::sort, etc.) do so without using std::function. They use templates.
    – interjay
    Commented Aug 26, 2020 at 12:27
  • 1
    That's the trade off. If you know the type, lots of optimizations can happen, but it makes the code more complex as you have to deal with the types. If you go "typeless" (std::function) then the code is easier to use, but you pay a performance penalty. Commented Aug 26, 2020 at 12:31

3 Answers 3

8

Lambdas are just objects that overload operator(). You can conceptually think of them as equivalent to:

class Lamba {
public:
    auto operator()(...) const { /* ... */ }
};

So, they are no more expensive than a similar function call. std::function is not necessary to use lambdas. You can use type deduction to store/pass them around:

template <typename Func>
void foo(Func&& func) { /* ... */ }

For non capturing lambdas, you can convert them to function pointers implicitly or explicitly with operator+:

void (*fp)(int) = [](int){ /* ... */ };

The reason why your question is hard to answer is that you are asking if lambdas are expensive. But, in comparison with what? If you want to know if they are adequately performant in your specific case, you're going to have to do some profiling for yourself and figure it out.

1
  • "compared to what" - I gave a rough reference mark, though. There are broader, practical ways of looking at things, some things can sometimes be classified as prohibitively costly for a whole class of scenarios without measuring in nanoseconds how bad it actually is, and my suspicion was this could be such a case. You won't insist on measuring with a zero-drift microvoltmeter whether to replace a battery or not.
    – sktpin
    Commented Aug 27, 2020 at 10:24
2

std::function is good, because it has easy syntax (much easier than function pointers) and it's self-documenting what type of function is required (compared to templates, which need explicit documentation). I suppose introductory articles about lambdas will use it, because the cost is not visible until you have certain restrictions.

There are two other ways to accept lambda as parameter: function pointers and templates.

Non capturing lambda can be converted to function pointer implicitly:

struct FuncyClass
{   unsigned func(int(*fnx)(int)) 
    {   return 2 * fnx(7);
    }
};

int main()
{   FuncyClass fc;
    auto result = fc.func( [](auto x){return x*3;} );
    
    printf("Result: %u\n", result);
    return 0;
}

Capturing and non-capturing lambdas can be passed with the help of templates:

struct FuncyClass
{   
    template<typename Func>
    unsigned func(Func&& fnx)
    {   return 2 * fnx(7);
    }
};

int main()
{   FuncyClass fc;
    int multiplier = 3;
    auto result = fc.func( [multiplier](auto x){return x*multiplier;} );
    
    printf("Result: %u\n", result);
    return 0;
}
1
  • It's said can be passed as template but you not actually pass anything to template definition, leaving compiler to infer substitutions from lamda. It could be confusing
    – Sugar
    Commented Aug 26, 2020 at 12:41
1

“Lambdas are cheap”

This is a relative notion.

And in practice it depends a lot on your C++ compiler.

Try to use a recent GCC (in 2020, use GCC 10), and enable warnings and optimizations, so compile your C++ code -on the command line- with g++ -Wall -Wextra -O2 at least.

Then benchmark or profile your application

On Linux, see time(7) and consider using gprof(1) or perf(1) (of course once you did debug your program with GDB). With other operating systems and compilers, find some equivalent.

This draft report may suggest you what optimizations a good compiler could do (and sometimes don't, because of Rice's theorem). It might sometimes happen that a lambda application becomes inlined.

If you love the functional programming paradigm, consider also using Ocaml, Common Lisp (SBCL) or Haskell. You'll find cases where there could be in practice slightly faster than C++ (with GCC or Clang), in particular on Linux for single threaded programs.

As a rule of thumb, I tend to believe that a C++ lambda is cheap as soon as its body is doing some significant work, like iterating (or searching) in some C++ container. If the lambda body is just making an integer addition, the overhead is significant (unless the compiler is clever enough to inline it). If it is doing some find operation on a std::map with thousands of entries, or actually using dynamic allocation (so some new, often using malloc) it usually is unsignificant.

In practice, you need to profile your application since YMMV.

3
  • Considering the initial impression I got that std::function is hardly avoidable to use in conjunction with lambdas, and that I would estimate most people consider "dynamic allocations can occur" as "not cheap", it did not seem all that vague to me. Basically the "yes, it really is cheap" "proof" I was hoping for was to be shown that using lambdas without std::function is both posisble and practical - I hoped that that sentiment came through in my post.
    – sktpin
    Commented Aug 26, 2020 at 12:54
  • 1
    @sktpin: If your question is really about whether you can use lambdas without capturing them in a std::function (or similar allocation-based operation), then maybe you should adjust your title and question text accordingly, rather than using questions about what is "cheap" and so forth. Commented Aug 26, 2020 at 18:26
  • The motivation behind the question is I want to know whether lambdas can be practically used "cheaply". Something that looked to be in the way of that was std::function, hence the focus. My understanding of it might be wrong though, and there may also be more aspects I'm missing. So just asking about std::function would perhaps not give the whole picture that's actually needed to make practical decisions. Why is the thinking on SO such that only questions on a subatomic level are considered good? It almost seems it's expected you can basically answer your question yourself.
    – sktpin
    Commented Aug 27, 2020 at 10:15

Not the answer you're looking for? Browse other questions tagged or ask your own question.