How to implement a dynamic function call with C ++ 11 and C ++ 14?

Here is the code that I hope explains what I want to achieve.

vector<int> ints; vector<double> doubles; struct Arg { enum Type { Int, Double }; Type type; int index; }; template <typename F> void Call(const F& f, const vector<Arg>& args) { // TODO: // - First assert that count and types or arguments of <f> agree with <args>. // - Call "f(args)" } // Example: void copy(int a, double& b) { b = a; } int test() { Call(copy, {{Int, 3}, {Double, 2}}); // copy(ints[3], double[2]); } 

Can this be done in C ++ 11?
If yes, is it possible to simplify the solution in C ++ 14?

+7
c ++ c ++ 11 templates c ++ 14 variadic-templates
source share
5 answers

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>... )> // auto in C++14 my_wrap( void(*f)(Ts...) ) { return [f](second_type<Ts,MyArg>...args){ f(args...); }; } 

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.

+10
source share

First of all, you need some kind of mechanism to register the values โ€‹โ€‹of the arguments, which later refer to some type and index:

 class argument_registry { public: // register a range of arguments of type T template <class T, class Iterator> void register_range(Iterator begin, Iterator end) { // enclose the range in a argument_range object and put it in our map m_registry.emplace(typeid(T), std::make_unique<argument_range<T, Iterator>>(begin, end)); } template <class T> const T& get_argument(size_t idx) const { // check if we have a registered range for this type auto itr = m_registry.find(typeid(T)); if (itr == m_registry.end()) { throw std::invalid_argument("no arguments registered for this type"); } // we are certain about the type, so downcast the argument_range object and query the argument auto range = static_cast<const argument_range_base1<T>*>(itr->second.get()); return range->get(idx); } private: // base class so we can delete the range objects properly struct argument_range_base0 { virtual ~argument_range_base0(){}; }; // interface for querying arguments template <class T> struct argument_range_base1 : argument_range_base0 { virtual const T& get(size_t idx) const = 0; }; // implements get by queyring a registered range of arguments template <class T, class Iterator> struct argument_range : argument_range_base1<T> { argument_range(Iterator begin, Iterator end) : m_begin{ begin }, m_count{ size_t(std::distance(begin, end)) } {} const T& get(size_t idx) const override { if (idx >= m_count) throw std::invalid_argument("argument index out of bounds"); auto it = m_begin; std::advance(it, idx); return *it; } Iterator m_begin; size_t m_count; }; std::map<std::type_index, std::unique_ptr<argument_range_base0>> m_registry; }; 

What we define a small type for combining type and numeric index for references to arguments:

 typedef std::pair<std::type_index, size_t> argument_index; // helper function for creating an argument_index template <class T> argument_index arg(size_t idx) { return{ typeid(T), idx }; } 

Finally, we need a template recursion to pass through all the expected function arguments, check if the user passed the argument of the corresponding type and requested it from the registry:

 // helper trait for call function; called when there are unhandled arguments left template <bool Done> struct call_helper { template <class FuncRet, class ArgTuple, size_t N, class F, class... ExpandedArgs> static FuncRet call(F func, const argument_registry& registry, const std::vector<argument_index>& args, ExpandedArgs&&... expanded_args) { // check if there are any arguments left in the passed vector if (N == args.size()) { throw std::invalid_argument("not enough arguments"); } // get the type of the Nth argument typedef typename std::tuple_element<N, ArgTuple>::type arg_type; // check if the type matches the argument_index from our vector if (std::type_index{ typeid(arg_type) } != args[N].first) { throw std::invalid_argument("argument of wrong type"); } // query the argument from the registry auto& arg = registry.get_argument<arg_type>(args[N].second); // add the argument to the ExpandedArgs pack and continue the recursion with the next argument N + 1 return call_helper<std::tuple_size<ArgTuple>::value == N + 1>::template call<FuncRet, ArgTuple, N + 1>(func, registry, args, std::forward<ExpandedArgs>(expanded_args)..., arg); } }; // helper trait for call function; called when there are no arguments left template <> struct call_helper<true> { template <class FuncRet, class ArgTuple, size_t N, class F, class... ExpandedArgs> static FuncRet call(F func, const argument_registry&, const std::vector<argument_index>& args, ExpandedArgs&&... expanded_args) { if (N != args.size()) { // unexpected arguments in the vector throw std::invalid_argument("too many arguments"); } // call the function with all the expanded arguments return func(std::forward<ExpandedArgs>(expanded_args)...); } }; // call function can only work on "real", plain functions // as you could never do dynamic overload resolution in C++ template <class Ret, class... Args> Ret call(Ret(*func)(Args...), const argument_registry& registry, const std::vector<argument_index>& args) { // put the argument types into a tuple for easier handling typedef std::tuple<Args...> arg_tuple; // start the call_helper recursion return call_helper<sizeof...(Args) == 0>::template call<Ret, arg_tuple, 0>(func, registry, args); } 

Now you can use it as follows:

 int foo(int i, const double& d, const char* str) { printf("called foo with %d, %f, %s", i, d, str); // return something return 0; } int main() { // prepare some arguments std::vector<int> ints = { 1, 2, 3 }; std::vector<double> doubles = { 10., 20., 30. }; std::vector<const char*> str = { "alpha", "bravo", "charlie" }; // register them argument_registry registry; registry.register_range<int>(ints.begin(), ints.end()); registry.register_range<double>(doubles.begin(), doubles.end()); registry.register_range<const char*>(str.begin(), str.end()); // call function foo with arguments from the registry return call(foo, registry, {arg<int>(2), arg<double>(0), arg<const char*>(1)}); } 

Real-time example: http://coliru.stacked-crooked.com/a/7350319f88d86c53

This construct should be open to any type of argument without having to list all supported types somewhere.

As noted in the comments on the code, you cannot call any called object like this in the general case, since at run time in C ++ it is not possible to execute an overload resolution.

+1
source share

I have a partial solution using C ++ 11 grammar.

First, I create an overloader function that takes the types of the arbitrator arguments

 template< typename Function > struct overloader : Function { overloader( Function const& func ) : Function{ func } {} void operator()(...) const {} }; template< typename Function > overloader<Function> make_overloader( Function const& func ) { return overloader<Function>{ func }; } 

then using a reloader to trick the compiler into believing that the following code (in a blocking block) is legal:

 template <typename F> void Call( F const& f, const vector<Arg>& args ) { struct converter { Arg const& arg; operator double&() const { assert( arg.type == Double ); return doubles[arg.index]; } operator int() const { assert( arg.type == Int ); return ints[arg.index]; } converter( Arg const& arg_ ): arg( arg_ ) {} }; auto function_overloader = make_overloader( f ); unsigned long const arg_length = args.size(); switch (arg_length) { case 0 : function_overloader(); break; case 1 : function_overloader( converter{args[0]} ); break; case 2 : function_overloader( converter{args[0]}, converter{args[1]} ); break; case 3 : function_overloader( converter{args[0]}, converter{args[1]}, converter{args[2]} ); break; /* case 4 : . . . case 127 : */ } } 

and test it as follows:

 void test_1() { Call( []( int a, double& b ){ b = a; }, vector<Arg>{ Arg{Int, 3}, Arg{Double, 2} } ); } void test_2() { Call( []( double& b ){ b = 3.14; }, vector<Arg>{ Arg{Double, 0} } ); } void my_copy( int a, double& b, double& c ) { b = a; c = a+a; } void test_3() { //Call( my_copy, vector<Arg>{ Arg{Int, 4}, Arg{Double, 3}, Arg{Double, 1} } ); // -- this one does not work Call( []( int a, double& b, double& c ){ my_copy(a, b, c); }, vector<Arg>{ Arg{Int, 4}, Arg{Double, 3}, Arg{Double, 1} } ); } 

problems with this solution:

  • g ++ 5.2 accept it, clang ++ 6.1 doesn
  • when the argument to the Call / function is not legal, it is silent.
  • the first argument to the Call function cannot be a C-style function, you need to wrap this in a lambda object for it to work.

The code is available here - http://melpon.org/wandbox/permlink/CHZxVfLM92h1LACf - for you.

+1
source share

I offer this answer after my comment on your question. Seeing that in the requirements you stated:

It is advisable that we do not need to create a structure that lists all the types that we want to support.

This may mean that you would like to get rid of the type enumerator in your Arg structure. Then only the meaning remains: then why not use simple C ++ types directly and not wrap them?

It is assumed that you know all your argument types at compile time
(This assumption may be very wrong, but I did not see any requirements in your question that impede this. I would be glad to rewrite my answer if you give more detailed information).

Solution of variational template C ++ 11

Now to the solution, using C ++ 11 variation templates and perfect forwarding. In the file Call.h:

 template <class F, class... T_Args> void Call(F f, T_Args &&... args) { f(std::forward<T_Args>(args)...); } 

Solution Properties

This approach seems to satisfy all your explicit requirements:

  • Works with C ++ 11 standard
  • Checks the number and types of arguments f with args .
    • In fact, this happens at the beginning, at compile time, instead of possibly using runtime.
  • No need to manually list accepted types (actually works with any type of C ++, whether it's native or user-defined)

Not in your requirement, but nice to have:

  • Very compact as it uses the built-in functions introduced in C ++ 11.
  • accepts any number of arguments
  • The type of the argument and the type of the corresponding parameter f should not exactly match, but must be compatible ( just like a normal C ++ function call).

Usage example

You can test it in a simple main.cpp file:

 #include "Call.h" #include <iostream> void copy(int a, double& b) { b = a; } void main() { int a = 5; double b = 6.2; std::cout << "b before: " << b << std::endl; Call(copy, a, b); std::cout << "b now: " << b << std::endl; } 

To print:

 b before: 6.2 b now: 5 
0
source share

Instead of clarifying the question, as I asked, you put it for generosity. Also, if this is really a question, i.e. homework without use, just an exercise in general basic programming, except that only obvious luck will give you the answer to your real question: people should be aware that the problem should be solved. This is the reason why no one was worried, even with generosity, to present a solution, when once-obvious mistakes have been corrected, the extremely trivial question that you are literally raising is, namely, how to do just that:

 vector<int> ints; vector<double> doubles; struct Arg { enum Type { Int, Double }; Type type; int index; }; template <typename F> void Call(const F& f, const vector<Arg>& args) { // TODO: // - First assert that count and types or arguments of <f> agree with <args>. // - Call "f(args)" } // Example: void copy(int a, double& b) { b = a; } int test() { Call(copy, {{Int, 3}, {Double, 2}}); // copy(ints[3], double[2]); } 

In C ++ 11 and later, one very direct way:

 #include <assert.h> #include <vector> using std::vector; namespace g { vector<int> ints; vector<double> doubles; } struct Arg { enum Type { Int, Double }; Type type; int index; }; template <typename F> void Call(const F& f, const vector<Arg>& args) { // Was TODO: // - First assert that count and types or arguments of <f> agree with <args>. assert( args.size() == 2 ); assert( args[0].type == Arg::Int ); assert( int( g::ints.size() ) > args[0].index ); assert( args[1].type == Arg::Double ); assert( int( g::doubles.size() ) > args[1].index ); // - Call "f(args)" f( g::ints[args[0].index], g::doubles[args[1].index] ); } // Example: void copy(int a, double& b) { b = a; } auto test() { Call(copy, {{Arg::Int, 3}, {Arg::Double, 2}}); // copy(ints[3], double[2]); } namespace h {} auto main() -> int { g::ints = {000, 100, 200, 300}; g::doubles = {1.62, 2.72, 3.14}; test(); assert( g::doubles[2] == 300 ); } 

In C ++ 14, there are no particularly relevant new features.

0
source share

All Articles