template<class...Sigs>
strucct functions:std::function<Sigs>...{
using std::function<Sigs>::operator()...;
template<class T,
std::enable_if<!std::is_same<std::decay_t<T>,fundtions>{}>,int> =0
>
functions(T&&t):
std::function<Sigs>(t)...
{}
};
the above is a C++17 sketch of a crude object that cam store more than one operator()
.
A more efficient one would only store the object once, but store how to call it many ways. And I skipped many details.
It isn't really a std::function
, but a compatible type; std function only stores one way to call the object.
Here is a "function view" that takes any number of signatures. It does not own the to-be-called object.
template<class Sig>
struct pinvoke_t;
template<class R, class...Args>
struct pinvoke_t<R(Args...)> {
R(*pf)(void*, Args&&...) = 0;
R invoke(void* p, Args...args)const{
return pf(p, std::forward<Args>(args)...);
}
template<class F, std::enable_if_t<!std::is_same<pinvoke_t, std::decay_t<F>>{}, int> =0>
pinvoke_t(F& f):
pf(+[](void* pf, Args&&...args)->R{
return (*static_cast<F*>(pf))(std::forward<Args>(args)...);
})
{}
pinvoke_t(pinvoke_t const&)=default;
pinvoke_t& operator=(pinvoke_t const&)=default;
pinvoke_t()=default;
};
template<class...Sigs>
struct invoke_view:pinvoke_t<Sigs>...
{
void* pv = 0;
explicit operator bool()const{ return pv; }
using pinvoke_t<Sigs>::invoke...;
template<class F, std::enable_if_t<!std::is_same<invoke_view, std::decay_t<F>>{}, int> =0>
invoke_view(F&& f):
pinvoke_t<Sigs>(f)...
{}
invoke_view()=default;
invoke_view(invoke_view const&)=default;
invoke_view& operator=(invoke_view const&)=default;
template<class...Args>
decltype(auto) operator()(Args&&...args)const{
return invoke( pv, std::forward<Args>(args)... );
}
};
Live example.
I use C++17 using ...
because the binary tree implementation in C++14 is ugly.
For your use case, it would looke like:
auto func_object = [](int i = 0){};
invoke_view<void(), void(int)> f1 = func_object;
std::function<void(int)> f3 = f1; // works
std::function<void()> f4 = f1; // works
note that the lack of lifetime management in invoke_view
means that the above only works when func_object
continues to exist. (If we make an invoke view to an invoke view, the "inner" invoke view is stored by pointer as well, so must continue to exist; not the case if we store the invoke view in a std function).
Lifetime management of the target, done right, takes a bit of work. You'd want to use a small buffer optimization with an optional smart pointer or something to get reasonable performance with small lambdas and avoid the overhead of the heap allocation.
A simple naive always heap allocating solution would replace the void*
with a unique_ptr<void, void(*)(void*)>
and store { new T(t), [](void* ptr){static_cast<T*>(ptr)->~T();} }
in it (or similar).
That solution makes the function object move-only; making it copyable requires also type erasing a clone operation.