Boost logo

Boost :

Subject: Re: [boost] [Boost-interest] C++ library for runtime-concepts (type-erasure)
From: Matus Chochlik (chochlik_at_[hidden])
Date: 2011-02-17 07:18:57


On Tue, Feb 15, 2011 at 9:38 PM, Daniel Larimer <dlarimer_at_[hidden]> wrote:
> I think I may have a solution for defining erasures with the total "cost" of a virtual method invocation and the
> memory model of boost::any.
>
> DEFINE_ERASURE( Calculator,  (add)(sub),
>        virtual double add(double) = 0;
>        virtual double sub(double) = 0;
>  )
>
> Details are provided in the discussion below.
>
[snip/]

>>> Color me very interested in how you achieved this.  Though what I would
>>> really like to see is:
>>
>> Basically the Mirror's registering macros create the following
>> two helper structures for every member variable:
>>
>> template <typename X>
>> struct _by_name_typ
>> {
>>    typedef X member_variable_name;
>> };
>>
>> template <typename X>
>> struct _by_name_val
>> {
>>    X member_variable_name;
>> };
>
> That was essentially my approach in Boost.IDL (New name suggestions?)

I came up with the idea when I was playing with Loki's
typelists and class generators so in the old version
of Mirror the tool which did that transformation described
above was also called a glass generator (but it's an ugly
name for a library)

>
>>
>> then Mirror let's you choose the type(s) for X and compose them into
>> a single class via multiple inheritance. Currently only the 'by_name'
>> template class uses this to allow you to get access to member
>> variable meta-data via its name at compile time (although I could
>> not find too many use cases where this is really necessary to do :))
>>
>>>
>>> struct some_type
>>> {
>>>    std::string append( std::string, double );
>>> };
>>>
>>> Converted Into functionally equivalent =>
>>>
>>> struct new_some_type
>>> {
>>>    future<string> append( string, double )
>>>    {
>>>        future<string> fut;
>>>        m_socket->async_sendto( serialize( s, d ),
>>>            host, boost::bind(wait_for_response, fut );
>>>        return fut;
>>>    }
>>> };
>>
>> This is doable. Mirror does not implement it yet but
>> during the work on my master's thesis I've implemented
>> a simple RPC mechanism that did something very
>> similar to the above using reflection.
>>
>
> Doable you say..  Perhaps, but at what cost?  Unlike member variables, there are a huge number of permutations that must be supported for each name that you wish to consider.
>
> template<typename Signature, bool IsConst=false, bool IsVirtual=false, bool isStatic=false>
> struct _by_name_method{}
>
> template<typename OnClass, RTN(ARG1,...)>
> struct _by_name_method : virtual public OnClass {
>        RTN some_name(ARG1,...) {  return static_cast<OnClass*>(this)->impl->some_name(ARG1,...)   }
> }

This is similar to how the RPC that I mentioned was implemented.
It was pre-C++0x so it depended heavily on preprocessor tricks
and recursive type-lists. I think that with variadic templates
it can be implemented more 'nicely'.

>
> Then you run into the problem of specifying the implementation of each method in terms of the object they are being composed on to.  In the example above I show an example using virtual inheritance and require that OnClass provide impl->name(args), this effectively just moved our problem.
>
> Applied to type erasure:
>
> class TypeErasure
> {
>        public:
>                template<typename ModelImpl>
>                TypeErasure( ModelImpl m )
>                {
>                        impl = new Model<ModelImpl>();
>                }
>
>                RTN some_name(ARGS...) { impl->some_name(); }
>
>        private:
>                struct Concept {
>                        virtual RTN some_name(ARGS...) = 0;
>                };
>                template<typename M>
>                struct Model : public M, public Concept {};
> };
>
> You need to define the method some_name() two different times, once in the concept and once in the TypeErasure.
>
> You would need to do something like this:
> template<typename VirtualBase, typename RTN(ARG1,...)>
> struct _by_name_method : VirtualBase {
>        RTN some_name(ARG1,...)
>        {
>                return  ((VirtualBase*)this)->some_name(args...);
>        }
> }
>
> So I suppose that if you did not mind creating  4 x (MAX PARAM) template specializations PER NAME to allow for const/nonconst virtual/nonvirtual overrides, then it would be ok.  I guess, with veridic templates (which I am not familiar with due to lack of compiler support), this whole issue might go away.

This is basically how I plan to do it, although I'm considering
to use wrapped member pointers. There are some tradeoffs I'll
have to write some code using both approaches and compare
them.

I think that to do the type erasure you would create an
abstract interface for the Model. You would register it
with Mirror which would create the _by_name_mem_fn
wrappers and then use some template meta-programming
to create a class using the Model as an interface and
derive from the individual _by_name_mem_fn instantiations
which would implement the individual member functions.

>
> In my alternative of converting methods into function<Signature> member variables, this "implementation" is dynamically provided by generating a suitable function object in the visitor.
>
> I guess with the approach outlined above you could achieve the following "automatic TypeErasure" generation:
>
> DEFINE_ERASURE( Calculator,  (add)(sub),
>        virtual double add(double) = 0;
>        virtual double sub(double) = 0;
>  )
>
> Implemented as something like:
>
> #define DEFINE_ERASURE( NAME,  METHOD_SEQ, ... )
> namespace detail { namespace NAME { namespace methods {
>        SEQ_FOR_EACH( DEFINE_BY_NAME_METHOD )
> }
>        struct Concept {
>                __VA_ARGS__
>        };
> } }
> class NAME :
>        SEQ_FOR_EACH( INHERIT_BY_NAME_METHOD )
> {
>        public:
>                template<typename CONCEPT>
>                NAME( CONCEPT c )
>                :impl( new Model<CONCEPT>(c) ){}
>
>        protected:
>                scoped_ptr<Concept> impl;
>        private:
>                template<typename M>
>                struct Model : public M, public detail::NAME::Concept {}
> };
>
> To improve performance on non-variadic template supporting compilers, you could specify the number of parameters like so:
> DEFINE_ERASURE( Calculator,  ((add,1))((sub,3)),
>        virtual double add(double) = 0;
>        virtual double sub(double,int,string) = 0;
>  )
>
> So my quick reply turned into a brainstorm dump.   Hopefully the ideas are useful.  I would like feedback on how such an implementation would be received by the community.
>
>
> I suspect that this would be the most efficient solution, but it would still fail to provide some of the benefits of my member function object solution, specifically, generating RPC client stubs or scripting engine hooks or any other task that requires "per-method" data.
>
> The method also forces you to list every method twice, once in the erasure and once in your type that implements the erasure.  In situations such as building an RPC server, you only want to define the class once, then expose it with as little redundant information as possible.  So I suspect that there is probably room for both types of erasure.
>
>
[snip/]

Matus


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk