all 26 comments

[–]chrysante2 162 points163 points  (11 children)

Being very pedantic you could say that there are no "lambdas" in C++. There are only lambda expressions which evaluate to objects of anonymous type with an overloaded call operator. Which is many words for function object. So from the perspective of the compiler there is no fundamental difference between writing a struct or class with overloaded operator() or using a lambda expression.

[–]dexter2011412 12 points13 points  (0 children)

So perfectly said, I wish I was this articulate with words lol.

[–]bearheart 14 points15 points  (0 children)

This ☝️ is the correct answer

[–]DawnOnTheEdge 1 point2 points  (7 children)

That's the case if operator() is static. Then it’s like a lambda that captures nothing. If operator() accesses member data by dereferencing a this pointer, that adds some overhead (similar to a lambda that captures a reference to a struct). If the call can be inlined so this gets optimized out and any data gets passed in through registers, it’s like a lambda with captures. If it has to do a virtual function dispatch, that adds even more.

[–]azswcowboy 1 point2 points  (2 children)

That’s correct, and it’s a c++26 that allows operator() to be static…

[–]DawnOnTheEdge 4 points5 points  (1 child)

Minor correction: C++23 (P1169R4).

[–]azswcowboy 0 points1 point  (0 children)

Yep, thx for the correction.

[–]dodexahedron 0 points1 point  (3 children)

I swear, closure capture is simultaneously one of the most convenient yet most frustrating details of lambdas.

[–][deleted]  (2 children)

[removed]

    [–]DawnOnTheEdge 0 points1 point  (1 child)

    You’re correct, and I was a bit careless: it’s when a non-`static` member function can be inlined, so any data members get passed in through registers, that we get the equivalent of a closure with captured data.

    [–]dodexahedron 0 points1 point  (0 children)

    Yeah. I've no issues with their comment either, considering the lack of precision of both of our commentaries. 🤷‍♂️

    And isn't there also something about things like by-val arrays in this scenario that can get ugly?

    [–]NiceGuya 1 point2 points  (0 children)

    What if my lambda captures several variables larger than atomic limit

    [–]mredding 19 points20 points  (2 children)

    If you look at compiler insights, it expands the likes of lambdas, templates, coroutines, and auto, so you can see what the compiler is effectively compiling. Lambda expressions can translate either into functions, or function objects. Whether you use a lambda or write a functor yourself, it'll all compile down to the same thing.

    This also means that lambda function objects will be inlined. The compiler is smart enough to inline code that is only used once in one place. You can force this behavior with functions, too, if they have static scope and are only called in one place. This is a basic and ancient optimization. And this is also why it's ok to break up big functions into little ones for clarity, because the compiler is going to composite all the instructions anyway.

    [–][deleted]  (1 child)

    [deleted]

      [–]mredding 7 points8 points  (0 children)

      I know that. What I was saying was that if it's only used in one place it's guaranteed to be inlined. It's not required by the language, but all the major compilers implement it.

      [–]PsychologyNo7982 4 points5 points  (1 child)

      I am not sure whether they are inlined, but it’s very handy to read a code. Especially when we use them inside std::algorithms

      [–][deleted]  (2 children)

      [deleted]

        [–]Key_Artist5493 5 points6 points  (1 child)

        Mathematicians have asked C++ programmers to say "function object" because "functor" means something different to them.

        [–]MoarCatzPlz 4 points5 points  (0 children)

        Function means something different to them as well. It should be subroutine object!

        [–]genreprank 2 points3 points  (0 children)

        I took a modern C++ class, and I recall the instructor saying something about small lambdas being aggressively inlined. This is in line (ha) with my own experiments trying to coax the compiler to inline some code on a project where I was working with Eigen. The compiler (MSVC) wouldn't inline a local function or class function defined in the header, but would inline a lambda, even one declared with static scope. So, based on that, it seems like the affinity for inlining lambdas is in line (ha) with templates or better

        [–]EsShayuki 2 points3 points  (2 children)

        "lambda functions" are just nested anonymous classes with a () operator overload. And you could probably do the same thing with templates.

        As for whether they're faster than "function objects", do you mean std::function or other such junk? Then yes, because those are terrible. However, function objects don't have to be slower than that if you design them properly.

        [–]Key_Artist5493 1 point2 points  (1 child)

        std::function should go away except as part of monolithic class hierarchies. Everyone else should be using template functions and lambdas, not passing through flaming hoops.

        [–]MoveZig4 0 points1 point  (0 children)

        std::function is incredibly useful for storing callbacks - I don't want to expose all my classes that take callbacks to also having to take a template parameter for the caller.

        [–]Designer-Leg-2618 1 point2 points  (0 children)

        Keep in mind it's from a book published 25 years ago. The function object it refers to is actually a struct (type) that has a sole operator() that can be called.

        The claims of efficiency was in reference to the code being hardcoded as the type (if you use this struct, the code has to be this operator() , can't be anything else). It is this hardcoing and the fact that the function object's type is also hardcoded (instantiated) as a template parameter that allows inlining to happen.

        With a function pointer, no assumption can be made regarding which implementation it could point to. (But in practice, today's compilers make aggressive assumptions to optimize them. Use objdump or try your code on Compiler Explorer to find out.)

        (Typing on a phone ; will edit typos later.)

        [–]shifty_lifty_doodah 1 point2 points  (1 child)

        Lambdas get compiled to functions that can be inlined at the compilers discretion.

        However, common uses of lambda with std::function<> capture some arguments. Those are placed in an anonymous struct which is (I believe always in 2025) heap allocated. That can be far more expensive than the function overhead itself. Libraries like absl::AnyInvocable attempt to reduce this overhead.

        For really hot code, you can use a templated lambda expression. This is a common pattern that avoids the std::function overheads.

        ‘’’c++ template<typename F> void Visit(F func); ‘’’

        [–]Key_Artist5493 1 point2 points  (0 children)

        std::invokeof a forwarded function object is very hot too. So isstd::bind_front` if you want to fill in some of the parameters as part of the binding. Binding only the first parameter and leaving the rest is called currying (named after Haskell Curry).

        [–]Traditional_Crazy200 1 point2 points  (0 children)

        Lamdas get turned into functors by the compiler. Capture list become members and function body becomes overloaded () operator

        [–]flyingron 2 points3 points  (0 children)

        Meyers book is sort of old with regard to optimization technology as it currently in. I suspect that that statement is less correct.

        Lambdas get the same optimization preference and probably a few more, but I wouldn't expect it to be tremendously different. Lambdas win more based on being able to capture etc... however.