Variant invocations of function members using C++11


This post addresses the case of wrapping functions for reflection for the aim of supporting run-time invocation through variant parameters.  This is the case of reflection frameworks and codes that makes C++ classes accessible to scripting runtimes such as Boost.Python.

I will focus on the case of exposed functions that are bound to a specific instance.

The approach taken here is based on constructs of C++11, in particular the variadic templates, std::function type, and auto return types.


The objective is to create an "Operator" class templated over a std::function containing the reference to the bound function member. The Operator class is derived from OperatorBase and should support dynamic invocation based on the boost::any variant type:

class OperatorBase
{
public:
 virtual void call(std::vector<boost::any> & params,boost::any & a) = 0;
};

The creation of an Operation should be as simple as follows:

addOperation("draw",&Shape::draw,this);

in the case we want to expose the method draw of class Shape:
class DynamicInvoke
{
protected:
 std::vector<std::shared_ptr<OperatorBases> > operators;
};

class Shape: public DynamicInvoke
{
public:

    void draw(int x, int y)
    {
         ...
    }
    
    Shape()
    {
       addOperation("draw",&amp;Shape::draw,this);
    }
};

Binding This

The first piece of element that is needed the the binding of a function member pointer to the associated this pointer. The std::bind function has indeed the limit of requiring to specify all the parameters of the function, not allowing to generalize to arbitrary functions.

The following bindthis function allows to bind the this pointer to the member function:

template<class R, class U, class... Args>
auto bindthis(R (U::*fx)(Args...), U * this) -> decltype(bindthissub(fx,this,make_int_sequence< sizeof...(Args) >{}))
{
 return bindthissub(fx,this,make_int_sequence< sizeof...(Args) >{});
}

The function takes a function member pointer fx and an instance this and it returns std::bind with all the required parameters replaced by placeholders. This is obtained through an helper function as follows:

template<class R, class U, class... Args, std::size_t... Is>
auto bindthissub(R (U::*fx)(Args...), U * this, int_sequence<Is...>) -> decltype(std::bind(fx, this, placeholder_template<Is>{}...))
{
 return std::bind(fx, this, placeholder_template<Is>{}...);
}

The approach uses a template "placeholder_template" to generate all the required placeholders based on the Is variadic variable that contains a sequence of indices from 1 to the number of arguments. The Is sequence is built through the known "make_int_sequence" pattern of C++11 variadic templates:

template<std::size_t...> struct int_sequence {};

template<std::size_t N, std::size_t... Is> struct make_int_sequence
    : make_int_sequence<N-1, N-1, Is...> {};
template<std::size_t... Is> struct make_int_sequence<0, Is...>
    : int_sequence<Is...> {};

For completeness the placeholder generation is the known code:

template<int>
struct placeholder_template
{};

namespace std
{
    template<int N>
    struct is_placeholder<placeholder_template<N> >
        : integral_constant<int, N+1> 
    {};
}

Adding the Operator

The addition takes the name, the function member pointer and the instance. In the following we use the getfunctioner to extract a generic function from the member function pointer. Finally the object is added to a list:

template <class T, class Y>
 void addOperation(const char * name, T a, Y  b)
 {
  typedef typename getfunctioner<T>::target target_t;
  typename getfunctioner<T>::target x = bindthis(a, b);
  operators.push_back(std::shared_ptr<OperatorBase>(new Operator<target_t>(x)));
 }


For completeness the getfunctioner is defined as follows, with the interesting point of matching a member function by template specialization:

template< class T>
struct getfunctioner {};

template< class R, class U, class...Args>
struct getfunctioner<R (U::*)(Args...) > {
 typedef std::function<R(Args...)> target;
};

Invoking the Operator

The invocation is interesting because for every argument it has to pick the argument from an array and cast to the original type through boost::any_cast:

template <class X>
struct call_n_args {};

virtual void call(std::vector<boost::any> & params,boost::any & r)
 {
  if(params.size() != arity<T>::value)
  {
   std::cout << "argument count mismatch\n";
   throw std::exception();
  }
  call_n_args<T>::call(fx_,params,r, make_int_sequence< arity<T>::value >{});
 }

This is obtained using the call_n_args class that does the trick:

template <class R, class... Args>
struct call_n_args<std::function<R(Args...) > >
{
  template <typename Container,std::size_t... Is>
  static void
  call(std::function<R(Args...)> & f, Container& c, boost::any & r,int_sequence<Is...> )
  {
  r = f( boost::any_cast<Args>(c.at(Is))...);
  }
};

Note that the expansion is applied at the level of the argument, using two variadic template types in the same expression. They should have the same length.

Also note that any_cast wants an exact cast otherwise it generates an exception.

For completeness the above uses the known arity class:

template <class X>
struct arity {};

template<class R, class... Args>
struct arity<R(Args...) >
{
 enum  { value = sizeof...(Args) };
};

template<class R, class... Args>
struct arity<std::function<R(Args...)> >
{
 enum  { value = sizeof...(Args) };
};

Final remarks

This approach can be combined with the extraction of the type properties of the function, allowing to provide inspection at run-time. A complementary work is the one to adapt it to the case of non-bound member functions, that has the advantage of reducing memory and run-time cost.

In addition to dynamic invocation the proposed approach can be used for the creation of proxies, e.g. for remoting, without using an external tool. The registered method can be used for introspecting the classes, and producing the header files for the proxy generation.

An example code can be seen here: https://gist.github.com/eruffaldi/815aaa0f9333abea3a70

Comments

Popular posts from this blog

Docker for our ROS robotic overlords

cmakego: Simpler access to external libraries in CMake

Algebrical Data Types in C++