I would do it in two steps.
First, I would wrap f in an object capable of understanding Arg parameters, and generate errors on failure. For simplicity, suppose we quit.
This is a little easier than your Arg to understand on this layer, so I could translate Arg into MyArg :
struct MyArg { MyArg(MyArg const&)=default; MyArg(int* p):i(p){} MyArg(double* p):d(p){} MyArg(Arg a):MyArg( (a.type==Arg::Int)? MyArg(&ints.at(a.index)): MyArg(&doubles.at(a.index)) ) {} int * i = nullptr; double* d = nullptr; operator int&(){ if (!i) throw std::invalid_argument(""); return *i; } operator double&(){ if (!d) throw std::invalid_argument(""); return *d; } };
We draw void(*)(Ts...) to std::function<void(MyArg, MyArg, MyArg)> as follows:
template<class T0, class T1>using second_type = T1; template<class...Ts> std::function<void( second_type<Ts,MyArg>... )>
now all that remains is counting the number of function parameters versus the number of vector sizes and unpacking std::vector into a function call.
The last one looks like this:
template<class...Ts, size_t...Is> void call( std::function<void(Ts...)> f, std::index_sequence<Is...>, std::vector<Arg> const& v ) { f( v[Is]... ); } template<class...Ts> void call( std::function<void(Ts...)> f, std::vector<Arg> const& v ) { call( std::move(f), std::index_sequence_for<Ts...>{}, v ); }
where index_sequence and index_sequence_for are C ++ 14, but equivalents can be implemented in C ++ 11 (there are many implementations for stack overflows).
So, we got something like:
template<class...Ts> void Call( void(*pf)(Ts...), std::vector<Arg> const& v ) { if (sizeof...(Ts)>v.size()) throw std::invalid_argument(""); auto f = my_wrap(pf); call( std::move(f), v ); }
Working with throws remains as an exercise, as it handles the returned values.
This code has not been compiled or tested, but the design must be reliable. It only supports call function pointers - calling generic callable objects is complicated because counting the number of arguments they want (like int or double) is complicated. If you pass in how many arguments they want as a compile-time constant, it's easy. You can also create a magic switch that processes the counter to a certain constant (10, 20, 1000, etc.) and sends the length of the vector to the compile-time constant, which generates an argument length mismatch.
This is harder.
Hard coded pointers seem to suck.
template<class...Ts>struct types{using type=types;}; template<size_t I> using index=std::integral_constant<size_t, I>; template<class T, class types> struct index_in; template<class T, class...Ts> struct index_in<T, types<T,Ts...>>: index<0> {}; template<class T, class T0, class...Ts> struct index_in<T, types<T0,Ts...>>: index<1+index_in<T, types<Ts...>>{}> {};
is a package of types.
Here's how we can store buffers:
template<class types> struct buffers; template<class...Ts> struct buffers<types<Ts...>> { struct raw_view { void* start = 0; size_t length = 0; }; template<class T> struct view { T* start = 0; T* finish = 0; view(T* s, T* f):start(s), finish(f) {} size_t size() const { return finish-start; } T& operator[](size_t i)const{ if (i > size()) throw std::invalid_argument(""); return start[i]; } } std::array< raw_view, sizeof...(Ts) > views; template<size_t I> using T = std::tuple_element_t< std::tuple<Ts...>, I >; template<class T> using I = index_of<T, types<Ts...> >; template<size_t I> view<T<I>> get_view() const { raw_view raw = views[I]; if (raw.length==0) { return {0,0}; } return { static_cast<T<I>*>(raw.start), raw.length/sizeof(T) }; } template<class T> view<T> get_view() const { return get_view< I<T>{} >(); } template<class T> void set_view( view<T> v ) { raw_view raw{ v.start, v.finish-v.start }; buffers[ I<T>{} ] = raw; } };
Now we are modifying Call :
template<class R, class...Args, size_t...Is, class types> R internal_call( R(*f)(Args...), std::vector<size_t> const& indexes, buffers<types> const& views, std::index_sequence<Is...> ) { if (sizeof...(Args) != indexes.size()) throw std::invalid_argument(""); return f( views.get_view<Args>()[indexes[Is]]... ); } template<class R, class...Args, size_t...Is, class types> R Call( R(*f)(Args...), std::vector<size_t> const& indexes, buffers<types> const& views ) { return internal_call( f, indexes, views, std::index_sequence_for<Args...>{} ); }
which is C ++ 14, but most components can be translated into C ++ 11.
This uses a search in O (1), lack of maps. You are responsible for filling buffers<types> buffers, for example:
buffers<types<double, int>> bufs; std::vector<double> d = {1.0, 3.14}; std::vector<int> i = {1,2,3}; bufs.set_view<int>( { i.data(), i.data()+i.size() } ); bufs.set_view<double>( { d.data(), d.data()+d.size() } );
Out-of-range parameter mismatch and index out of range generate errors. It works only with raw function pointers, which simplifies working with anything with a fixed (non-template) signature (for example, std::function ).
Making work with an object without a signature is more difficult. Basically, instead of relying on a function called by arguments, you instead create a cross product of types<Ts...> to some fixed size. You build a (large) table from which they are valid calls for the passed in target call (at compile time), then at runtime go through this table and determine if the arguments were passed valid for calling object c.
It is getting messy.
This is why my version above just asks for indexes and infers types from the called object.