×

Search anything:

Implement std::function in C++

C++

Binary Tree book by OpenGenus

Open-Source Internship opportunity by OpenGenus for programmers. Apply now.

First, I introduce you to the std::function and then proceed to the implementation

Class template std::function is a general-purpose polymorphic function wrapper. Instances of std::function can store, copy, and invoke any CopyConstructible Callable target:

  • functions
  • lambda expressions
  • bind expressions
  • pointers to member functions
  • pointers to data members.
  • or other function objects

std::function is introduced in C++ 17
The stored callable object is called the target of std::function. If a std::function contains no target, it is called empty. Invoking the target of an empty std::function results in std::bad_function_call exception being thrown.

For using std::function you need to

#include <functional>


List of different ways and types that you can store in std::function:

Functions

void print_num(int i)
{
    std::cout << i << '\n';
}

int main()
{
     // store a function via function pointer
    std::function<void(int)> func = print_num;
    func(-9); 
}
>> -9

Lambda expressions

int main()
{
    // store a lambda
    std::function<void()> func = []() { std::cout << 30 << '\n'; };
    func();
}
>> 30

Bind expressions

void print_num(int i)
{
    std::cout << i << '\n';
}

int main()
{
    // store the result of a call to std::bind
    std::function<void()> func = std::bind(print_num, 31337);
    func();
    
    // you also can use std::bind with class objects
}
>> 31337

Pointers to member functions

struct Foo {
    Foo(int number) : num(number) {}
    void print_add(int i) const
        { std::cout << num + i << '\n'; }

    int num;
};

int main()
{
    // store a call to a member function
    std::function<void(const Foo&, int)> func = &Foo::print_add;
    const Foo foo(314159);
    func(foo, 1);
}
>> 314160

Pointers to data members

struct Foo {
    Foo(int number) : num(number) {}
    void print_add(int i) const
        { std::cout << num + i << '\n'; }

    int num;
};

int main()
{
    // store a call to a data member accessor
    std::function<int(Foo const&)> func = &Foo::num;
    
    const Foo foo(314159);
    std::cout << "num: " << func(foo) << '\n';
}
>> num: 314159

Other function objects

struct PrintNum {
    void operator()(int i) const
    {
        std::cout << i << '\n';
    }
};
 
int main()
{
    // store a call to a function object
    std::function<void(int)> func = PrintNum();
    func(18);
    
    //and others
>> 18

std::function is cool BUT !!!

If you check the performance using a pure function pointer and std::function. A pure function pointer is much faster in almost all cases. When using std::function, the compiler cannot optimize the function call, because std::function encapsulates it.

Also, sometimes a std::function can allocate memory on the heap, this happens when the callable object is too large. Therefore, you should also think that the copy construct can also allocate memory

std::function can avoid allocations by using a small bit of stack space in each object

  • Small buffer optimisation (the same principle as std::string)
  • If a callable thing is bigger than the stack space then std::function will get some memory from the heap
  • The size of the stack space is implementation defined

So think about whether you really need it. Of course, if you need to keep some functions and classes together, it is extrimly useful

Implementation

Below are examples of std-function implementation. There is also a way to implement without inheritance, but it is very confusing and shows itself worse in benchmarks compared to other implementations. You can find the full code at the link at the end of the page

ALL BENCHMARKS ARE LISTED UNDER THE CODE

Define a template class, then pass the function signature you want into that template parameter

As I said earlier std::function use a little buffer optimization, so for the first time let's try to implement something like that.

// we need these headers
#include <memory>
#include <array>

template <typename>
class function;

template <typename Result, typename... Arguments>
class function<Result (Arguments...)> 
{
// ...
}

So how we need to store out callable object? All our classes must contain different classes, for example, a pointer to a function, a lambda function (remember that for each lambda function a new class is created), or others.

Therefore, we create a FunctorHolderBase class that can contain any Callable types using Polymorphism. Then we create a FunctorHolder template type for each new argument type that is passed to our std::function implementation.

So, as a result, our class stores a pointer to our FunctorHolderBase class.

template <typename ReturnType, typename... Args>
struct FunctorHolderBase
{
    virtual ~FunctorHolderBase() {}
    virtual ReturnType operator()(Args&&...) = 0;
    virtual void copyInto (void*) const = 0;
    virtual FunctorHolderBase<Result, Arguments...>* clone() const = 0;
};

template <typename Functor, typename ReturnType, typename... Args>
struct FunctorHolder final : FunctorHolderBase<Result, Arguments...>
{
    FunctorHolder (Functor func) : f (func) {}

    ReturnType operator()(Args&&... args) override
    {
        return f (std::forward<Arguments> (args)...);
    }

    void copyInto (void* destination) const override
    {
        new (destination) FunctorHolder (f);
    }

    FunctorHolderBase<Result, Arguments...>* clone() const override
    {
        return new FunctorHolder (f);
    }

    Functor f;
};
    
FunctorHolderBase<Result, Arguments...>* functorHolderPtr = nullptr;

So in our construct we check if we can save our template value in stack memory. If so we store it on stack otherwise we store it on heap.

typename std::aligned_storage<32>::type stack;

template <typename Functor>
function (Functor f)
{
    if (sizeof (FunctorHolder<Functor, Result, Arguments...>) <= sizeof (stack))
    {
        functorHolderPtr = (decltype (functorHolderPtr)) std::addressof (stack);
        new (functorHolderPtr) FunctorHolder<Functor, Result, Arguments...> (f);
    }
    else
    {
        functorHolderPtr = new FunctorHolder<Functor, Result, Arguments...> (f);
    }
}

Other code that we needed

Copy constructor, destructor, overload assign operator, overload ()operator

function (const function& other)
{
    if (other.functorHolderPtr != nullptr)
    {
        if (other.functorHolderPtr == (decltype (other.functorHolderPtr)) std::addressof (other.stack))
        {
            functorHolderPtr = (decltype (functorHolderPtr)) std::addressof (stack);
            other.functorHolderPtr->copyInto (functorHolderPtr);
        }
        else
        {
            functorHolderPtr = other.functorHolderPtr->clone();
        }
    }
}
function& operator= (function const& other)
{
    if (functorHolderPtr != nullptr)
    {
        if (functorHolderPtr == (decltype (functorHolderPtr)) std::addressof (stack))
            functorHolderPtr->~FunctorHolderBase();
        else
            delete functorHolderPtr;

        functorHolderPtr = nullptr;
    }

    if (other.functorHolderPtr != nullptr)
    {
        if (other.functorHolderPtr == (decltype (other.functorHolderPtr)) std::addressof (other.stack))
        {
            functorHolderPtr = (decltype (functorHolderPtr)) std::addressof (stack);
            other.functorHolderPtr->copyInto (functorHolderPtr);
        }
        else
        {
            functorHolderPtr = other.functorHolderPtr->clone();
        }
    }

    return *this;
}
function() = default;

~function()
{
    if (functorHolderPtr == (decltype (functorHolderPtr)) std::addressof (stack))
        functorHolderPtr->~FunctorHolderBase();
    else
        delete functorHolderPtr;
}
Result operator() (Arguments&&... args) const
{
    return (*functorHolderPtr) (std::forward<Arguments> (args)...);
}

So using this implementation we have this benchmark result

Can we do better?

Only heap allocated ?

Also I check the method that allocates only to the heap.
Here are the method that are different in this variant

template <typename Functor>
function (Functor f)
    : functorHolderPtr (new FunctorHolder<Functor, Result, Arguments...> (f))
{}

function (const function& other)
{
    if (other.functorHolderPtr != nullptr)
        functorHolderPtr = other.functorHolderPtr->clone();
}

function& operator= (function const& other)
{
    delete functorHolderPtr;

    if (other.functorHolderPtr != nullptr)
        functorHolderPtr = other.functorHolderPtr->clone();

    return *this;
}

~function()
{
    delete functorHolderPtr;
}

Only stack allocated ?

And I check the method that allocates only to the stack.
Here are the method that are different in this variant


template <typename Functor>
function (Functor f)
{
    static_assert (sizeof (FunctorHolder<Functor, Result, Arguments...>) <= sizeof (stack), "Too big!");
    functorHolderPtr = (FunctorHolderBase<Result, Arguments...>*) std::addressof (stack);
    new (functorHolderPtr) FunctorHolder<Functor, Result, Arguments...> (f);
}

function (const function& other)
{
    if (other.functorHolderPtr != nullptr)
    {
        functorHolderPtr = (FunctorHolderBase<Result, Arguments...>*) std::addressof (stack);
        other.functorHolderPtr->copyInto (functorHolderPtr);
    }
}

function& operator= (function const& other)
{
    if (functorHolderPtr != nullptr)
    {
        functorHolderPtr->~FunctorHolderBase<Result, Arguments...>();
        functorHolderPtr = nullptr;
    }

    if (other.functorHolderPtr != nullptr)
    {
        functorHolderPtr = (FunctorHolderBase<Result, Arguments...>*) std::addressof (stack);
        other.functorHolderPtr->copyInto (functorHolderPtr);
    }

    return *this;
}

~function()
{
    if (functorHolderPtr != nullptr)
        functorHolderPtr->~FunctorHolderBase<Result, Arguments...>();
}

Benchmarks

Screenshot-from-2022-10-22-20-10-57

Non type erased

As you can see in the benchmark histogram, there is an extremely fast implementation of std::function, so what does it look like. It's very simple, it's a simple pointer to a function:

template <typename>
class function;

template <typename Result, typename... Arguments>
class function<Result (Arguments...)>
{
public:
    template <typename Functor>
    function (Functor f)
        : functionPtr  (f)
    {}

    function() = default;

    Result operator() (Arguments&&... args) const
    {
        return functionPtr (std::forward<Arguments> (args)...);
    }

    Result(*functionPtr)(Arguments...) = nullptr;
};

As I said, it's really just a function pointer. And if you use this approach, the compiler can optimize some things, but if we use the type erasure method, it becomes very difficult for the compiler to do optimizations with this

With this article at OpenGenus, you must have the complete idea about std::function in C++. Enjoy.


Helpful resources:

Implement std::function in C++
Share this