A complex, efficient replacement for std::function<R(Args...)> not difficult to write.
Since we are rooted, we want to avoid memory allocation. Therefore, I will write small_task< Signature, size_t sz, size_t algn > . It creates an sz sized buffer and algn alignment in which it stores its erased objects.
It also stores the engine, destroyer, and pointer to the invoker function. These pointers can be locally within small_task (maximum locality) or in the struct vtable { /*...*/ } const* table manual.
template<class Sig, size_t sz, size_t algn> struct small_task; template<class R, class...Args, size_t sz, size_t algn> struct small_task<R(Args...), sz, algn>{ struct vtable_t { void(*mover)(void* src, void* dest); void(*destroyer)(void*); R(*invoke)(void const* t, Args&&...args); template<class T> static vtable_t const* get() { static const vtable_t table = { [](void* src, void*dest) { new(dest) T(std::move(*static_cast<T*>(src))); }, [](void* t){ static_cast<T*>(t)->~T(); }, [](void const* t, Args&&...args)->R { return (*static_cast<T const*>(t))(std::forward<Args>(args)...); } }; return &table; } }; vtable_t const* table = nullptr; std::aligned_storage_t<sz, algn> data; template<class F, class dF=std::decay_t<F>,
living example .
Another thing that is missing is the high quality void(Args...) , which doesn't care if the return value is pass-in callable.
The above task supports moving but not copying. Adding a copy means that everything saved must be copied and requires a different function in the vtable (with an implementation similar to move , except for src is const and no std::move ).
A small amount of C ++ 14 was used, namely the aliases enable_if_t and decay_t and the like. They can be easily written in C ++ 11 or replaced with typename std::enable_if<?>::type .
bind best replaced with lambdas, honestly. I do not use it even on non-embedded systems.
Another improvement is to teach him how to handle small_task , which are less / less aligned, keeping their vtable pointer and not copying it to the data buffer, and wrapping it in another vtable . This will facilitate the use of small_tasks , which are hardly large enough for your problem.
Converting member functions to function pointers is not only undefined behavior; often the calling convention of a function is different from a member function. In particular, this is passed in a particular register under certain calling conventions.
Such differences can be subtle and can occur when changing unrelated code or changing the version of the compiler or something else. Therefore, I would avoid this if you have few other choices.
As already noted, the platform lacks libraries. Each use of std above is tiny, so I just write:
template<class T>struct tag{using type=T;}; template<class Tag>using type_t=typename Tag::type; using size_t=decltype(sizeof(int));
move
template<class T> T&& move(T&t){return static_cast<T&&>(t);}
forward
template<class T> struct remove_reference:tag<T>{}; template<class T> struct remove_reference<T&>:tag<T>{}; template<class T>using remove_reference_t=type_t<remove_reference<T>>; template<class T> T&& forward( remove_reference_t<T>& t ) { return static_cast<T&&>(t); } template<class T> T&& forward( remove_reference_t<T>&& t ) { return static_cast<T&&>(t); }
Decay
template<class T> struct remove_const:tag<T>{}; template<class T> struct remove_const<T const>:tag<T>{}; template<class T> struct remove_volatile:tag<T>{}; template<class T> struct remove_volatile<T volatile>:tag<T>{}; template<class T> struct remove_cv:remove_const<type_t<remove_volatile<T>>>{}; template<class T> struct decay3:remove_cv<T>{}; template<class R, class...Args> struct decay3<R(Args...)>:tag<R(*)(Args...)>{}; template<class T> struct decay2:decay3<T>{}; template<class T, size_t N> struct decay2<T[N]>:tag<T*>{}; template<class T> struct decay:decay2<remove_reference_t<T>>{}; template<class T> using decay_t=type_t<decay<T>>;
is_convertible template
template<class T> T declval(); // no implementation template<class T, T t> struct integral_constant{ static constexpr T value=t; constexpr integral_constant() {}; constexpr operator T()const{ return value; } constexpr T operator()()const{ return value; } }; template<bool b> using bool_t=integral_constant<bool, b>; using true_type=bool_t<true>; using false_type=bool_t<false>; template<class...>struct voider:tag<void>{}; template<class...Ts>using void_t=type_t<voider<Ts...>>; namespace details { template<template<class...>class Z, class, class...Ts> struct can_apply:false_type{}; template<template<class...>class Z, class...Ts> struct can_apply<Z, void_t<Z<Ts...>>, Ts...>:true_type{}; } template<template<class...>class Z, class...Ts> using can_apply = details::can_apply<Z, void, Ts...>; namespace details { template<class From, class To> using try_convert = decltype( To{declval<From>()} ); } template<class From, class To> struct is_convertible : can_apply< details::try_convert, From, To > {}; template<> struct is_convertible<void,void>:true_type{};
enable_if
template<bool, class=void> struct enable_if {}; template<class T> struct enable_if<true, T>:tag<T>{}; template<bool b, class T=void> using enable_if_t=type_t<enable_if<b,T>>;
result_of
namespace details { template<class F, class...Args> using invoke_t = decltype( declval<F>()(declval<Args>()...) ); template<class Sig,class=void> struct result_of {}; template<class F, class...Args> struct result_of<F(Args...), void_t< invoke_t<F, Args...> > >: tag< invoke_t<F, Args...> > {}; } template<class Sig> using result_of = details::result_of<Sig>; template<class Sig> using result_of_t=type_t<result_of<Sig>>;
aligned_storage
template<size_t size, size_t align> struct alignas(align) aligned_storage_t { char buff[size]; };
is_same
template<class A, class B> struct is_same:false_type{}; template<class A> struct is_same<A,A>:true_type{};
living example , about a dozen lines on the std library template that I need.
I would put this “reimplementation of the std library” in the namespace notstd to make it clear what was going on.
If you can, use a linker that combines identical functions together, such as a gold linker. template metaprogramming can cause binary bloat without a solid linker to strip it.