Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How std::function works

Tags:

c++

c++11

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++?

like image 699
masoud Avatar asked Feb 18 '13 12:02

masoud


People also ask

What is the point of std :: function?

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.

Should std :: function be passed by value?

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& .

Is std :: function slow?

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.

Is std :: function heavy?

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).


1 Answers

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)

like image 131
R. Martinho Fernandes Avatar answered Sep 19 '22 06:09

R. Martinho Fernandes