Boost logo

Boost :

Subject: Re: [boost] Formal Review Request: TypeErasure
From: Daniel Larimer (dlarimer_at_[hidden])
Date: 2012-06-12 01:12:31


On Jun 8, 2012, at 9:47 AM, Hite, Christopher wrote:

>>
>> In a nutshell, concept-based-runtime-polymorphism/type-erasure allows you to have runtime polymorphism while preserving value semantics. The value of that win is very commonly underestimated.
>
> I think you're overselling it a bit here. You can have value sematics with interfaces, just pass around these and heap all the time in clone and maintain single points of ownership or shared_ptr. It has about the same cost as boost::function.
>
> struct interface{
> virtual ~interface()=0;
> virtual interface* clone()=0;
> };
>
>
> To be fair consider the version of any<..,_self&> which doesn't tackle storage and ownership and compare that with an interface with virtual functions. With the any<...,_self&> I've got a virtual table pointer and a pointer to the type-deleted object. With interface& I've got a pointer to an object with a virtual table intruding into it.

I suspect that you could achieve generic 'value semantics' for any pure virtual interface via a very simple macro + other C++11 features. I think the cost of making a call with the following code would be the same as using pointer semantics via a virtual call.

Consider the following to define value semantics for my_interface:
class my_interface {
  public:
    virtual ~my_interface() {};
    virtual int add( int ) = 0;
    virtual int add( double, std::string ) = 0;
    virtual int sub( int ) = 0;
};
BOOST_VALUE( my_interface, (add)(sub) )

struct test : virtual public my_interface {
    int add( int i ) { return i + 5; }
    int add( double, std::string ) { return 6; }
    int sub( int i ) { return i - 5; }
};

int main( int argc, char** argv ) {
  value<my_interface> v;
  v = test();

  std::cerr<<v.add(int(5))<<" == 10\n";
  std::cerr<<v.add(double(5.5), "hello")<<" == 6\n";
  std::cerr<<v.sub(int(5))<<" == 0\n";
  return 0;
}

The macro would expand to:
template<>
struct value<my_interface> : public value_base<my_interface> {
  using value_base<my_interface>::value_base<my_interface>;
  using value_base<my_interface>::operator=;
  
  // this is what the macro would most expand
  template<typename... Args>
  auto add(Args&&... args) -> decltype( ((my_interface*)0)->add( std::forward<Args>(args)... ) ) {
    return impl->add( std::forward<Args>(args)... );
  }
  // this is what the macro would most expand
  template<typename... Args>
  auto sub(Args&&... args) -> decltype( ((my_interface*)0)->sub( std::forward<Args>(args)... ) ) {
    return impl->sub( std::forward<Args>(args)... );
  }
};

Granted the 'only' downside to this approach is that it is 'intrusive'.

I have implemented as much here:
https://github.com/bytemaster/mace/blob/master/libs/stub/examples/value.cpp

You can achieve non-intrusive version with a slightly more verbose interface definition:

template<typename T=void,bool impl=false>
struct my_interface{
    virtual ~my_interface() {}
    virtual int add( int ) = 0;
    virtual int add( double, std::string ) = 0;
    virtual int sub( int ) = 0;
};
template<typename T>
struct my_interface<T,true> : public T, virtual public my_interface<>{
    template<typename... Args>
    my_interface(Args&& ...args):T( std::forward<Args>(args)... ){}
    virtual int add( int i) { return T::add(i); }
    virtual int add( double d, std::string s) { return T::add( d, s); }
    virtual int sub( int i) { return T::sub( i ); }
};
 BOOST_VALUE( my_interface, (add)(sub) )

struct test { /** no intrusive base class required **/
    int add( int i ) { return i + 5; }
    int add( double, std::string ) { return 6; }
    int sub( int i ) { return i - 5; }
};

int main( int argc, char** argv ) {
  value<my_interface> v;
  v = test();

  std::cerr<<v.add(int(5))<<" == 10\n";
  std::cerr<<v.add(double(5.5), "hello")<<" == 6\n";
  std::cerr<<v.sub(int(5))<<" == 0\n";
  return 0;
}

https://github.com/bytemaster/mace/blob/master/libs/stub/examples/value2.cpp

Other than the repetitive nature of the interface spec, it is very straight forward, clear, and easy to read and understand compared to how TypeErasure currently achieves such a feat.

Does this have value?? Could you integrate this technique with TypeErasure? Is it good enough for most use cases (non operator overloading)? It probably wouldn't be difficult to convert this to reference semantics and avoid heap alloc!


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