First I want to say thanks to this channel:
Weekly with Jason Turner
This channel promotes advanced learning materials on C++,
I’ve persnoally been in a lecture by Jason and it was one of my best learning experiences.
Binding functions and using delegates in a static compiled manner is no easy task.
Modern C++ moves from void*
semantics to more concrete types which make it possible to bind various functionality including lambdas.
In this post we’ll cover 3 basic lessons to start building our knowledge.
To understand these lessons we’ll need to ask ourselves what kind of goals are we trying to achieve.
Async code and event-base mechanisms need callbacks.
A Callback as a concept is the action that happens when something is done.
For example when a popcorn being made is done we grab popcorn.
In psuedo code:
1 | TakeAndEat(Popcorn) { NomNomNom(); } |
These 3 functionalities are the basics.
To read more about them in the C++ Reference library:
C++ Reference - std::invoke
C++ Reference - std::function
C++ Reference - std::bind
A callable is something that can be invoked,
What is invoke or invokable?
The C++ Reference tells us that INVOKE
should be valid:INVOKE<R>(f, std::declval<ArgTypes>()...)
R
. f
suits type T
which can be called. ArgTypes
complete for the arguments.What usually we call?
operator()
implementation)Callable objects are objects which can be called.
The operator()
overload makes the object itself to invoke a functionality just like a function delegate or a function.
1 | struct AdderPrinter |
Adding the Ctor this is how lambdas work.
By capturing the parameters beforehand we can create an instance we can use, copy and move.
1 | struct AdderPrinter |
Invokes a Callable
.
The most simple explenation is that the invoke
is able to differentiate between the different callables - accept them and invoke the function - either itself, or the operator ()
.
Example:
1 | void SayHi() |
A type that wraps a function - a state we can hold in class as a state.
This allows us to create a more type safe delegates.
1 | void SayHi() |
Bounding a function to arguments, it allows us to create partial callables or rearrange the parameters.
1 | template<class T> |
Any implementation of bind, function, invoke involves getting arguments of various types.
I’ll reference C#
implementation of generics and its in ability of variadic templates and why we need them, well… the docs will talk themselves:
I’ll just say it - we don’t like that.
Instead C++ generics implements it in a static compiled type manner.
Means the types should be known in compile time (There are some exceptions but I won’t get into that).
The goal is to be able to implement a bind with different types:
1 | InvokeMyFunc(Func,1,"Mah String", 0.2f, std::string("BBBbye")); |
Obviosuly implementing like that is possible:
1 | template<class Callable, class Tone> |
Variadic templates allow us to pack and unpack arguments.
This allow us to create multiple parameter functions very easy:
In C++ 17 you can use the fold expressions to allow it to be written like that:
1 | template<typename... TArgs> |
Before C++ 17 you’ll need a template closing method:
1 | template<class TOne> |
So the Invokeable is can easily be declared variadic for the arguments:
1 | template<class Callable,class... TArgs > |
This Function
will hold a simple pointer to a function.
1 | template<class TReturn, class... T> |
We can use it with a return type, arguments and lambdas.
1 | void Hello() |
Using a lambda capture we can capture member functions:
1 | A a; |
This isn’t the most sophisticated implementation.
We can deal it with std::bind
which we’ll dive deeper into it in the next part.
C++ 20 introduced std::mem_fn
for function callables of member functions!
In the next post we’ll learn more about the binding mechanism and dive deeper into forward
, decay
and semantics.
Thanks for reading!