I am writing some C++ code to interact with an external scripting language. I want to be able to register arbitrary C++ callable code as functions to be called from the script.
The scripting library offers an API to register function pointers with signature
std::string callback( std::vector<std::string> const& args);
ie any function called from the script is given an array of strings representing the functions input args and expects a String as a result.
Up to now I would create a wrapper function for each c++ function I want to expose to the script. e.g. for function
int add_two_numbers(int a, int b){ return a + b; }
//I create and register
string add_two_numbers_wrapper( std::vector<std::string> const& args)
{
auto arg0 = std::stoi( args[0] );
auto arg1 = std::stoi( args[1] );
auto result = add_two_numbers( arg0, arg1 );
return std::to_string(result);
}
Not wanting to write the wrapper by hand each time I've tried to create some templates to manage the boilerplate. I had some success using a number of template wrappers that are written for each set of different argument counts but I want to support 'any' number of args in future without creating a new template. This is my attempt at a one-size-fits-all function wrapper template.
Are there anyways of simplifying further? I'm not too bothered about performance at this juncture and the priority is ease of use.
#include <string_view>
#include <string>
#include <functional>
#include <vector>
#include <tuple>
#include <type_traits>
///Helper Template to convert to and from the script's value types.
//should support functions:
// from_arg - convert string to expected c++ argument type
// to_result - convert result back to script type.
template < typename TType >
struct convert_type;
//specialisation for int
template <> struct convert_type <int >
{
static int from_arg (std::string const &arg)
{
return std::stoi (arg);
}
static std::string to_result (int r)
{
return std::to_string (r);
}
};
template <> struct convert_type<void>
{
static std::string to_result()
{
return "";
}
};
//Wrapper template for adapting c++ function to function that is callable from script
template < typename > class fun_wrapper;
template < typename R, typename ... Args >
class fun_wrapper < R (Args ...) >
{
public:
using FunctionType = std::function < R (Args ...) >;
using ResultType = R;
using ArgTypes = std::tuple < Args ... >;
static constexpr int m_arg_count = sizeof ... (Args);
//constructor
fun_wrapper (FunctionType f)
:m_fun {f}
{
}
std::string operator ()( std::vector<std::string> const& inputs)
{
if constexpr( std::is_void_v<ResultType> ) //wrapped function returns void
{
if constexpr ( m_arg_count == 0) //wrapped function doesn't need args unpacking
{
m_fun();
}
else
{
unpack_args<0>( inputs );
}
std::string result = "";
return result;
}
else
{
ResultType r1;
if constexpr ( m_arg_count == 0) //wrapped function doesn't need args unpacking
{
r1 = m_fun();
}
else
{
r1 = make_call<0>(args);
}
std::string result = convert_type< ResultType >::to_result (r1);
return result;
}
}
//Helper template to unpack and convert each arg in inputs into a parameter pack that
//can then be passed into the function call
template < int index, typename... TArgs >
ResultType unpack_args( std::vector<std::string> const& inputs, TArgs... targs)
{
using CurrentArgType = std::tuple_element_t< index, ArgTypes >;
if constexpr (index + 1 == m_arg_count) //all args unpacked - call function
{
return m_fun( targs..., convert_type< CurrentArgType >::from_arg( inputs[index]) );
}
else
{
//unpack next argument in list and keep going
return make_call< index + 1 >(args, targs..., convert_type< CurrentArgType >::from_arg( inputs[index]));
}
}
private:
std::function< R(Args...) > m_fun;
};
//Example usage.
int add_numbers(int in1, int in2)
{
return in1 + in2;
}
int main ()
{
fun_wrapper< int(int, int) > add_numbers_wrapper(add_numbers);
std::vector<std::string> args = {"18", "24"};
cout << add_numbers_wrapper( args ) << endl; //outputs "42"
return 0;
}
std::index_sequence
that might be a bit clearer, but I'm not sure myself. github.com/sdilts/Salmon/blob/master/include/vm/… \$\endgroup\$