I\'m developing a simple event driven application in C++11 based on the publish/subscribe pattern. Classes have one or more onWhateverEvent()
method invoked by the
A solid, efficient, std::function
replacement isn't hard to write.
As we are embedded, we want to avoid allocating memory. So I'd write a small_task< Signature, size_t sz, size_t algn >
. It creates a buffer of size sz
and alignment algn
in which it stores its erased objects.
It also stores a mover, a destroyer, and an invoker function pointer. These pointers can either be locally within the small_task
(maximal locality), or within a manual struct vtable { /*...*/ } const* table
.
template
struct small_task;
template
struct small_task{
struct vtable_t {
void(*mover)(void* src, void* dest);
void(*destroyer)(void*);
R(*invoke)(void const* t, Args&&...args);
template
static vtable_t const* get() {
static const vtable_t table = {
[](void* src, void*dest) {
new(dest) T(std::move(*static_cast(src)));
},
[](void* t){ static_cast(t)->~T(); },
[](void const* t, Args&&...args)->R {
return (*static_cast(t))(std::forward(args)...);
}
};
return &table;
}
};
vtable_t const* table = nullptr;
std::aligned_storage_t data;
template,
// don't use this ctor on own type:
std::enable_if_t{}>* = nullptr,
// use this ctor only if the call is legal:
std::enable_if_t, R
>{}>* = nullptr
>
small_task( F&& f ):
table( vtable_t::template get() )
{
// a higher quality small_task would handle null function pointers
// and other "nullable" callables, and construct as a null small_task
static_assert( sizeof(dF) <= sz, "object too large" );
static_assert( alignof(dF) <= algn, "object too aligned" );
new(&data) dF(std::forward(f));
}
// I find this overload to be useful, as it forces some
// functions to resolve their overloads nicely:
// small_task( R(*)(Args...) )
~small_task() {
if (table)
table->destroyer(&data);
}
small_task(small_task&& o):
table(o.table)
{
if (table)
table->mover(&o.data, &data);
}
small_task(){}
small_task& operator=(small_task&& o){
// this is a bit rude and not very exception safe
// you can do better:
this->~small_task();
new(this) small_task( std::move(o) );
return *this;
}
explicit operator bool()const{return table;}
R operator()(Args...args)const{
return table->invoke(&data, std::forward(args)...);
}
};
template
using task = small_task;
live example.
Another thing missing is a high quality void(Args...)
that doesn't care if the passed-in callable has a return value.
The above task supports move, but not copy. Adding copy means that everything stored must be copyable, and requires another function in the vtable (with an implementation similar to move
, except src
is const
and no std::move
).
A small amount of C++14 was used, namely the enable_if_t
and decay_t
aliases and similar. They can be easily written in C++11, or replaced with typename std::enable_if>::type
.
bind
is best replaced with lambdas, honestly. I don't use it even on non-embedded systems.
Another improvement would be to teach it how to deal with small_task
s that are smaller/less aligned by storing their vtable
pointer rather than copying it into the data
buffer, and wrapping it in another vtable
. That would encourage using small_tasks
that are just barely large enough for your problem set.
Converting member functions to function pointers is not only undefined behavior, often the calling convention of a function is different than a member function. In particular, this
is passed in a particular register under some calling conventions.
Such differences can be subtle, and can crop up when you change unrelated code, or the compiler version changes, or whatever else. So I'd avoid that unless you have little other choice.
As noted, the platform lacks libraries. Every use of std
above is tiny, so I'll just write them:
templatestruct tag{using type=T;};
templateusing type_t=typename Tag::type;
using size_t=decltype(sizeof(int));
template
T&& move(T&t){return static_cast(t);}
template
struct remove_reference:tag{};
template
struct remove_reference:tag{};
templateusing remove_reference_t=type_t>;
template
T&& forward( remove_reference_t& t ) {
return static_cast(t);
}
template
T&& forward( remove_reference_t&& t ) {
return static_cast(t);
}
template
struct remove_const:tag{};
template
struct remove_const:tag{};
template
struct remove_volatile:tag{};
template
struct remove_volatile:tag{};
template
struct remove_cv:remove_const>>{};
template
struct decay3:remove_cv{};
template
struct decay3:tag{};
template
struct decay2:decay3{};
template
struct decay2:tag{};
template
struct decay:decay2>{};
template
using decay_t=type_t>;
template
T declval(); // no implementation
template
struct integral_constant{
static constexpr T value=t;
constexpr integral_constant() {};
constexpr operator T()const{ return value; }
constexpr T operator()()const{ return value; }
};
template
using bool_t=integral_constant;
using true_type=bool_t;
using false_type=bool_t;
templatestruct voider:tag{};
templateusing void_t=type_t>;
namespace details {
templateclass Z, class, class...Ts>
struct can_apply:false_type{};
templateclass Z, class...Ts>
struct can_apply>, Ts...>:true_type{};
}
templateclass Z, class...Ts>
using can_apply = details::can_apply;
namespace details {
template
using try_convert = decltype( To{declval()} );
}
template
struct is_convertible : can_apply< details::try_convert, From, To > {};
template<>
struct is_convertible:true_type{};
template
struct enable_if {};
template
struct enable_if:tag{};
template
using enable_if_t=type_t>;
namespace details {
template
using invoke_t = decltype( declval()(declval()...) );
template
struct result_of {};
template
struct result_of > >:
tag< invoke_t >
{};
}
template
using result_of = details::result_of;
template
using result_of_t=type_t>;
template
struct alignas(align) aligned_storage_t {
char buff[size];
};
template
struct is_same:false_type{};
template
struct is_same:true_type{};
live example, about a dozen lines per std
library template I needed.
I would put this "std library reimplementation" into namespace notstd
to make it clear what is going on.
If you can, use a linker that folds identical functions together, like the gold linker. template metaprogramming can cause binary bloat without a solid linker to strip it.