C++ Weekly - Ep 332 - C++ Lambda vs std::function vs Function Pointer

  Рет қаралды 35,923

C++ Weekly With Jason Turner

C++ Weekly With Jason Turner

Күн бұрын

Пікірлер: 80
@zamf
@zamf 2 жыл бұрын
The way I understand it is: - if you're just passing a callable somewhere use a lambda (pass it as a generic callable type) - if you have to store the callable object or its implementation depends on runtime information then: - if the callable is stateless, use a function pointer - if the callable has state, then use a std::function And since requiring your objects to be stateless is a big limitation, function pointers are almost never used except when you really need good performance is some hot code and you'd sacrifice flexibility to gain speed.
@Omnifarious0
@Omnifarious0 2 жыл бұрын
I've done stateful callables using lambdas with reference captures. So, it partly depends on the lifetime of the state you need. So, I'm not so sure your categorization system is the most useful for making a decision about which to use. If you're writing something that takes a templated parameter that you call, just use the ::std::invocable concept so people can use whatever they need. If you want to have a concrete type for your parameter, and you need the most flexibility possible, take a ::std::function and deal with the likely runtime cost. To make that cheaper, take a const reference to a ::std::function to avoid the handle/body copy (and consequent allocation or atomic reference count update) when it makes sense. If you're willing to sacrifice on flexibility for somewhat greater performance, and you must have a concrete type (i.e. it's not a template), take a function pointer as an argument. This means your caller can't use a lambda, or any number of other things (like ::std::bind1st or a member function pointer) as an argument. You can't specify a lambda as a concrete type, and because the code of the function would be implied by the type anyway, it wouldn't be useful to do so. So, if you want people to be able to pass in lambdas, you have to either have a template, or take a ::std::function. The choice of which to use is not so dependent on statefulness as it is on a combination of convenience, flexibility, and performance requirements. And the choice is more of a library or framework author's choice, not a 'normal developer' choice.
@frydac
@frydac 2 жыл бұрын
Another option is to use a template parameter to pass a stateful callable, which iirc can mitigate some of the overhead that an std::function can incur in certain circumstances. This can only be used when the callable type is known at compile time, which was the case e.g. in the examples used in this video.
@zamf
@zamf 2 жыл бұрын
@@Omnifarious0 By 'generic' I meant template parameter. You should always pass lambdas as template parameters.
@oschonrock
@oschonrock 2 жыл бұрын
@@zamf The STL takes them "by value", which is also a very valid, because any state should be small by design and "by value" eliminates lifetime issues.
@TheMrKeksLp
@TheMrKeksLp 2 жыл бұрын
Very intuitive system
@talhaabus
@talhaabus Жыл бұрын
Your english is clear and not complicated, thanks for all you did
@thestarinthesky_
@thestarinthesky_ Жыл бұрын
Is it accurate to say 'a lambda is just an anonymous class with an operator() overload. Captured parameters are class members. The constructor initializes them from the capture expression. The lambda itself is the operator() overload. The end result is an object. No different than any other object, as far as objects in C++ go. '?Thanks Jason. I've learned a lot.
@cppweekly
@cppweekly Жыл бұрын
That seems at least mostly accurate, yes. The compiler creates a closure object for you from a lambda expression, and the body of the lambda goes into the operator() overload. The main problem is that lambdas don't have constructors, the members are directly initialized.
@thestarinthesky_
@thestarinthesky_ Жыл бұрын
Thank you for the explanation@@cppweekly
@monamimani
@monamimani 2 жыл бұрын
For MSVC people that look into this make sure you use visual studio 17.2. Before it wasn't sometime inlining std::function. I believe this fixed it LWG-2774 2098 std::function construction vs assignment. And for performance, from my own micro benchmark, calling a lambda, a function pointer and a std::function, is as performant to calling a corresponding free function of member function.
@Omnifarious0
@Omnifarious0 2 жыл бұрын
I'm all about RISC-V, but I'm surprised you're using it for a demonstration on C++ weekly. Why choose that architecture?
@cppweekly
@cppweekly 2 жыл бұрын
It's an in-universe tie-in for my other channel: kzbin.info/www/bejne/hHbCp2eIZsmDg6M
@oschonrock
@oschonrock 2 жыл бұрын
Great episode. Would be nice to also contrast this with "passing a lambda by T&& or by value".
@irrationalnumbers771
@irrationalnumbers771 Жыл бұрын
Also something that I think is interesting that you can't do with function pointers is using std::bind to bind an instantiated object member function to a std::function not to mention the added flexibility of std::placeholders.
2 жыл бұрын
RISC-V has an extremely reduced instruction set, I would not be surprised if assigning to register and adding a value to "zero" and assigning the result to register would produce the same op-code. That is one of the purpose of having hard-coded register that is always zero, you can throw away a lot of special case op-codes.
@willwu7353
@willwu7353 2 жыл бұрын
Its really interesting, I should look into it more, I've always defaulted to compiling with ARM which is RISC cuz the assembly is easier to read.
@markuscwatson
@markuscwatson 2 жыл бұрын
Glad to have found your channel today
@grownupgaming
@grownupgaming 2 жыл бұрын
you are the man!
@niklassheth6995
@niklassheth6995 2 жыл бұрын
li x, y is just a psuedo instruction for addi x, zero, y so the two outputs are equivalent.
@Sebanisu
@Sebanisu 2 жыл бұрын
I was using std::function for a scope_guard but I ended up just using a template that takes in a lambda. I was getting exceptions and crashes I couldn't find out why. I'm only storing one function in my scope_guard so std::function was overkill. I couldn't use a function pointer, because I needed to capture some values.
@shiretu1
@shiretu1 2 жыл бұрын
`struct Location` can be eliminated and replaced with a concept at the site of use
@willwu7353
@willwu7353 2 жыл бұрын
Think a big issue is that std::function does heap allocation (it has to because it copies the guts of the callable and the arguments), but if your return types and arguments are sufficiently small then maybe std::move_only_function or SG14 inplace_function might be more efficient.
@BigPapaMitchell
@BigPapaMitchell 2 жыл бұрын
14:46 I would also argue that the overhead in this case is inconsequential. If you're running an expensive operation but it's cycling at human speeds, the overhead is most likely going to be meaningless. If I'm playing like an RPG or something most likely I'm only going through 2-4 tiles per second maximum, so your window for extra overhead is in the hundreds of milliseconds. Optimization where it doesn't need to be optimized IMO.
@hagenmuller4568
@hagenmuller4568 2 жыл бұрын
Thank you very much, Jason, for providing us with yet another excellent episode! There’s one minor issue I would have, though. At around 6:18 you say „... so the lambda is a function...“. Well, no. A lambda is an anonymous class, or an object thereof. I‘m pretty sure you meant to say it that way 😉 But given the episode title, I wanted to point that out. This is a misconception that I really encounter often with my colleagues.
@cppweekly
@cppweekly 2 жыл бұрын
oh I thought I corrected myself on that. Although arguably, I should have pointed out that a capture-less lambda is just a function pointer. I missed that opportunity.
@314Labs
@314Labs 9 ай бұрын
​@@cppweekly no you didn't miss it. 11:21
@SkitzFist1
@SkitzFist1 9 күн бұрын
Isn't lambdas defaulted to be constexpr as well?
@N....
@N.... 2 жыл бұрын
I hope someday they add a way to provide an allocator for std::function, since currently it can do its own secretive allocations that you have no control over.
@marksilverman
@marksilverman 2 жыл бұрын
do they leak memory?
@kvoistinov
@kvoistinov 2 жыл бұрын
@@marksilverman it can happen because type-erasure. Same problem with shared_ptr and allocate_shared.
@gtdcoder
@gtdcoder 2 жыл бұрын
What if there are no captures? I don’t think it allocates in that case.
@free_mind
@free_mind 2 жыл бұрын
This. We needed std::function on our FreeRTOS embedded platform. We were using custom allocators that wrapped FRTOS malloc/free, and we couldn't use std::function. Instead we used some template magic that wrapped the return type of std::bind into our own callable type that used our allocator. Of course, if the lambda has no capture libstdc++ will use the small buffer and no allocations will be performed. But it's not guaranteed by the standard so we didn't want to rely on it. Interestingly, std::function optionally supported custom allocators in the beginning but it wasn't mandatory to use them and no implementation actually utilized them.
@free_mind
@free_mind 2 жыл бұрын
@@kvoistinov I am sorry, but that sounds incorrect. Could you explain how std::function will leak memory itself, disregarding programmer error? Even though it's type erased, the type it is constructed with is known at compile-time. It has a templated constructor, so at construction time can allocate the object and instantiate templated deleter callback that will be called in the destructor. std::any is also type erased and does the same thing.
@openroomxyz
@openroomxyz 2 жыл бұрын
Not sure if I get this right, if optimizer does not optimize things, function pointer is the fastest, lambda is slower than function pointer, std::function is the slowest one? By using std::function you are trading speed for flaxibility.
@yato3335
@yato3335 2 жыл бұрын
Lambda is only slower than function pointers if it has captures. Otherwise lambda is just like an anonymous function.
@embeddor3023
@embeddor3023 2 жыл бұрын
@@yato3335 lambda is never slower. A function pointer is not optimizable by the compiler and thus neither inlinable nor reorderable and calls to it must always use specific calling conventions that might add extra overhead. The compiler basically treats it as a black box. A lambda call on the other hand is always a direct function call, thus inlinable, reorderable and in general susceptible to interprocedural optimization. Edit: fixed typos
@XxxGuitarMadnessxxX
@XxxGuitarMadnessxxX 2 жыл бұрын
@@yato3335 Exactly. Plus they can be super helpful if you need to type erase a value and later cast it back to the original type with an anonymous lambda -> say in the case of taking a void* to some data and stating the type in the lambda and storing that lambda in a member function pointer - that allows you to later call the function pointer and cast the value back to the original type and is really cool in my opinion lol It's what I'm doing for a project and I believe it's the same type of thing libfmt and do with their custom type handlers
@XxxGuitarMadnessxxX
@XxxGuitarMadnessxxX 2 жыл бұрын
@@embeddor3023 -I would have to disagree with that statement only due to the fact that a capture-less lambda is treated as a function pointer or as a direct function call whereas a lambda with captures must have auto storage to hold any values it captures- I'm confused by the first part and last part of your reply lol I completely agree with that last portion
@embeddor3023
@embeddor3023 2 жыл бұрын
@@XxxGuitarMadnessxxX well, if you agree with the last part then you already got the point of the first part.
@oracleoftroy
@oracleoftroy 2 жыл бұрын
I'd be interested to see std::move_only_function in the mix as well and maybe comparisons with things like std::bind_front (though that is starting to get out of scope of this talk).
@cppweekly
@cppweekly 2 жыл бұрын
Added to the list of topics.
@JohnDoe-vf1mt
@JohnDoe-vf1mt 2 жыл бұрын
Does anyone know by any chance whether this channel has videos regarding standard library (smart pointers, containers, etc) in all the details? I'd like to learn more about its internals and peculiarities. Btw, great video. Enjoyed it a lot.
@cppweekly
@cppweekly 2 жыл бұрын
Only here and there, no specifically intentional look into most of those things. When I try to no a multi-part series it always ends up a failure, so I just keep doing one-off kind of things :D
@sledgex9
@sledgex9 2 жыл бұрын
Any opinions on implementing callables via templates as T type? IIRC Boost.Asio uses it a lot for the async callback handlers. The downside is that the expected signature of the callable is poorly documented.
@grincha09
@grincha09 2 жыл бұрын
there is a post by philippegroarke named enforcing_signatures_on_templated_callbacks
@Sebanisu
@Sebanisu 2 жыл бұрын
C++ 20 has required expressions which you can use to test the passed in callable and check the return type. I used one the other day because I needed to know if a function was returning a value or void.
@free_mind
@free_mind 2 жыл бұрын
Sure, but what if you need to store the callable? If you just need to use the callable immediately in the same function then it can be a template type, even constrained by C++20 concepts. But if you need to store the callback for later, then at some point you will be forced to make a decision to store it in a concrete type. I am not sure what boost::asio does. Maybe boost doesn't have to store it at all. The callable type T could just be forwarded into other template functions until it's no longer used.
@r75shell
@r75shell 2 жыл бұрын
what do you mean by type erased wrapper? Is struct point {int x, y; }; type erased?
@DFPercush
@DFPercush 2 жыл бұрын
The parameters and return type are templated, so they're not type erased, but the anonymous type of a lambda, basically a new randomly named struct for each lambda you write, is what's being erased. As long as it has the right signature and operator() it can satisfy the concepts of a std::function. The capture list and any members necessary to implement captures also become opaque at that point.
@r75shell
@r75shell 2 жыл бұрын
@@DFPercush ok, but he says that type erased wrapper is std::function.
@cppweekly
@cppweekly 2 жыл бұрын
std::function is a type-erased wrapper around the callable thing. See here for more details: kzbin.info/www/bejne/rnu2fJ6VppdopbM
@r75shell
@r75shell 2 жыл бұрын
@@DFPercush sorry, I just had to learn what type-erasure means. Talk by Klaus Igelberger helped. And to clarify, types are everywhere. And the trick is to make interface, and make templates to produce implementation. And only interface is hidding real type, but any interface does. New name of technique 'type-erasure' just to distinguish it from basic interfaces.
@MaxCoplan
@MaxCoplan 2 жыл бұрын
It took me a while to realize this was RISC-V 😁
@fcolecumberri
@fcolecumberri 2 жыл бұрын
I wouldn't necessary say std::function has an “overhead,” I mean, you could use a pointer, but also if you don't know if the pointing position is valid (A.K.A. not nullptr), now you need to check that and you get the “overhead” back which is very small to begin with. Personally I'll always prefer a thown exception I can catch than a segmentation fault.
@anon_y_mousse
@anon_y_mousse 2 жыл бұрын
I'll always prefer an error code or a pointer I can check against null and not have the overhead of exceptions or segmentation faults.
@Omnifarious0
@Omnifarious0 2 жыл бұрын
Doesn't ::std::function need to use the handle/body idiom to work? And wouldn't that necessarily entail an allocation?
@edp70
@edp70 2 жыл бұрын
Re the "interesting RISC-V optimization" at 5:20, you might be amused to compare with the code shown at 20:30 in Adam Goucher's (very cool) talk "Deep Learning with the Analytical Engine" @ kzbin.info/www/bejne/n2qqfqihZcyZnK8
@homomorphic
@homomorphic 2 жыл бұрын
A captureless lambda is nothing but a function pointer, therefore the conversion is very easy ;-)
@antagonista8122
@antagonista8122 2 жыл бұрын
None at all. Captureless lambda is anonymous class without any member but compiler provides conversion into function pointer to extend flexibility. If calling is not inlined function pointer may invoke indirect call (additional overhead) while calling lambda/class operator() is direct.
@homomorphic
@homomorphic 2 жыл бұрын
@@antagonista8122 the conversion is trivial since a captureless lambda is just a function (and any function is nothing more than an address and an address is a pointer). Optimization is a different pass. Inlining of code means it is no longer a function of any sort.
@ShtrikeBirb
@ShtrikeBirb 2 жыл бұрын
Zero-overhead abstraction, yeah.
@YSoreil
@YSoreil 2 жыл бұрын
It's difficult for me as a viewer to judge what is impressive and what isn't about what a compiler does when it's some gimmick architecture being displayed.
@anon_y_mousse
@anon_y_mousse 2 жыл бұрын
It's not a gimmick architecture considering that real chips have been shipped that use it. At least two now and more in the works. And truthfully, it's a strange attitude to take towards understanding optimizations as these are pretty high level and should be understandable by more people by being more generic in usage. He could just as well have compiled to a VM and it would've worked as an example.
@DamianReloaded
@DamianReloaded 2 жыл бұрын
Who's gonna complain about the bright screen? X'D
@h.hristov
@h.hristov 2 жыл бұрын
me
@DamianReloaded
@DamianReloaded 2 жыл бұрын
don't be whiny this is free realstate
@unclechaelsneckvein
@unclechaelsneckvein Жыл бұрын
I didn't understand a single thing. What about instead of showing assembly, quickly and precisely say what the difference is bw the 3 things.
@joestevenson5568
@joestevenson5568 9 ай бұрын
Because the differences are somewhat complex and can be subtle.
C++ Weekly - Ep 333 - A Simplified std::function Implementation
14:52
C++ Weekly With Jason Turner
Рет қаралды 19 М.
C++ Weekly - Ep 421 - You're Using optional, variant, pair, tuple, any, and expected Wrong!
10:34
So Cute 🥰 who is better?
00:15
dednahype
Рет қаралды 19 МЛН
Quando eu quero Sushi (sem desperdiçar) 🍣
00:26
Los Wagners
Рет қаралды 15 МЛН
小丑女COCO的审判。#天使 #小丑 #超人不会飞
00:53
超人不会飞
Рет қаралды 16 МЛН
C++ Weekly - Ep 125 - The Optimal Way To Return From A Function
13:10
C++ Weekly With Jason Turner
Рет қаралды 78 М.
C++ Weekly - Ep 404 - How (and Why) To Write Code That Avoids std::move
8:50
C++ Weekly With Jason Turner
Рет қаралды 35 М.
AI Is Making You An Illiterate Programmer
27:22
ThePrimeTime
Рет қаралды 267 М.
C++ Weekly - Ep 343 - Digging Into Type Erasure
15:16
C++ Weekly With Jason Turner
Рет қаралды 20 М.
C++ Weekly - Ep 456 - RVO + Trivial Types = Faster Code
10:33
C++ Weekly With Jason Turner
Рет қаралды 16 М.
invoke_r Should Not Exist - C++ Weekly Ep 466
7:34
C++ Weekly With Jason Turner
Рет қаралды 401
31 nooby C++ habits you need to ditch
16:18
mCoding
Рет қаралды 862 М.
Linus just made this run 2.6% faster in Linux
1:48
Machinely
Рет қаралды 16 М.
So Cute 🥰 who is better?
00:15
dednahype
Рет қаралды 19 МЛН