You know, we can wrap or store a lambda function to a std::function
:
#include <iostream>
#include <functional>
int main()
{
std::function<float (float, float)> add = [](float a, float b)
// ^^^^^^^^^^^^^^^^^^^^
{
return a + b;
};
std::cout << add(1, 2) << std::endl;
}
My question is around std::function
, as you can see it is a template class but it can accept any kind of function signature.
For example float (float, float)
in this form return_value (first_arg, second_arg)
.
What's the structure of std::function
and how does it accept a function signature like x(y,z)
and how it works with it? Is float (float, float)
a new valid expression in C++?
Class template std::function is a general-purpose polymorphic function wrapper. Instances of std::function can store, copy, and invoke any Callable target -- functions, lambda expressions, bind expressions, or other function objects, as well as pointers to member functions and pointers to data members.
If there is any possibility you are storing a copy of the std::function , pass by value. Otherwise, either way is roughly equivalent: the only downside to by-value is if you are taking the same bulky std::function and having one sub method after another use it. Barring that, a move will be as efficient as a const& .
If it is small, like 3-5 CPU instructions then yes std::function will make it slower, because std::function is not inlined into outer calling code. You should use only lambda and pass lambda as template parameter to other functions, lambdas are inlined into calling code.
Note that when talking about overhead of std::function (i.e. that std::function is "heavy"), usually the performance overhead is meant. Its memory overhead will be minor (around 2 extra pointers, I'd guess).
It uses some type erasure technique.
One possibility is to use mix subtype polymorphism with templates. Here's a simplified version, just to give a feel for the overall structure:
template <typename T> struct function; template <typename Result, typename... Args> struct function<Result(Args...)> { private: // this is the bit that will erase the actual type struct concept { virtual Result operator()(Args...) const = 0; }; // this template provides us derived classes from `concept` // that can store and invoke op() for any type template <typename T> struct model : concept { template <typename U> model(U&& u) : t(std::forward<U>(u)) {} Result operator()(Args... a) const override { t(std::forward<Args>(a)...); } T t; }; // this is the actual storage // note how the `model<?>` type is not used here std::unique_ptr<concept> fn; public: // construct a `model<T>`, but store it as a pointer to `concept` // this is where the erasure "happens" template <typename T, typename=typename std::enable_if< std::is_convertible< decltype( t(std::declval<Args>()...) ), Result >::value >::type> function(T&& t) : fn(new model<typename std::decay<T>::type>(std::forward<T>(t))) {} // do the virtual call Result operator()(Args... args) const { return (*fn)(std::forward<Args>(args)...); } };
(Note that I overlooked several things for the sake of simplicity: it cannot be copied, and maybe other problems; don't use this code in real code)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With