views:

155

answers:

4

I have the following C++ design problem and I would really appreciate any suggestion/solution. Please notice that my background is not in computer science, so I may be missing some obvious solution.

The way I usually separate key components in the code is to define interfaces via abstract classes and pure virtual functions.

Example1:

class B
{
public:
    virtual double f( double x ) = 0;
};

class D1 : public B
{
public:
    double f( double x ) const 
    {
    return 0.0;
    }
};    

class D2 : public B
{    
public:
    double f( double x ) const 
    {
    return 1.0;
    }
};

This way I can nicely separate interface from implementation. This approach is also quite fast (and as what I am working on is a numerical library this is important :P).

Now, the problem I am facing is the following one.

I have a set of "functionalities" which can be summarized by functions (defined below) f(), g(), and h() Notice that all these functions will in general differ in arguments and return types.

Suppose I have some code that expects a pointer to an object that implements the functionalities f() and g(). What I would like to do is to being able to pass something which has "more or equal" functionalities, for example something which supports f(), g() and h().

To better explain myself here is some code. Please notice that instead of multiple inheritance I can have used a "nested intheritance" approach, like in boost::operators. The point here is that I will never have the case in which f() is the same as g (). All the features are different. Te problem is that in order to make this work I need to use reinterpret_cast as in the example below (so this is not really a solution):

Example2:

class F {
public:
    virtual double f( double x ) = 0;
};

class G {
public:
    virtual double g( double x ) = 0;
};

class H {
public:
    virtual double h( double x ) = 0;
};

class N {};

template<class T1, class T2=N, class T3=N>
class Feature : public T1 , public T2 , public T3
{
};

template<class T1, class T2>
class Feature<T1,T2,N> : public T1, public T2
{
};

template<class T1>
class Feature<T1,N,N> : public T1
{
};

//Supp for Supports/Implements
class SuppFandG : public Feature<F,G>
{
public:
    double f( double x ) { return 0.0; }
    double g( double x ) { return 1.0; } 
};

class SuppFandH : public Feature<F,H>
{
public:
    double f( double x ) { return 0.0; }
    double h( double x ) { return 1.0; } 
};

class SuppFandGandH : public Feature<F,G,H>
{
public:
    double f( double x ) { return 0.0; }
    double g( double x ) { return 1.0; }
    double h( double x ) { return 2.0; }
};

int main()
{
    Feature<F,G>* featureFandGPtr;
    Feature<F,H>* featureFandHPtr;
    Feature<H,F>* featureHandFPtr;
    Feature<F,G,H>* featureFandGandHPtr;

    SuppFandGandH suppFandGandH;
    featureFandGandHPtr = &suppFandGandH;

    //featureFandGPtr = featureFandGandHPtr; //Illegal. static_cast illegal too.
    //the reason to do this is that I would like to pass a pointer to an object 
    //of type Feature<F,G,H> to a function (or constructor) that expects a pointer to Feature<F,G> 
    featureFandGPtr = reinterpret_cast< Feature<F,G>* >( featureFandGandHPtr );
    featureFandHPtr = reinterpret_cast< Feature<F,H>* >( featureFandGandHPtr );
    featureHandFPtr = reinterpret_cast< Feature<H,F>* >( featureFandGandHPtr );

    featureFandGPtr->f( 1.0 );
    featureFandGandHPtr->h( 1.0 );
}

Or I can try to construct a inheritance hierarcy but changing the definition of Feature but the following example makes the Visual studio 2008 professional compiler crash, so I cannot test it.

Example 3:

//This will not work, Visual studio 2008 professional crash.
template<class T1, class T2=N, class T3=N>
class Feature : public Feature<T1,T2> , public Feature<T1,T3> , public Feature<T2,T3>
{
};

template<class T1, class T2>
class Feature<T1,T2,N> : public Feature<T1>, public Feature<T2>
{
};

template<class T1>
class Feature<T1,N,N> : public T1
{
};

With this approach I still have the problems 1) Feature is logically equivalent (for what I want to achieve) to Feature but their types are different. This can however be solved by some fancy metaprogramming using the MPL boost library (always "sort" the types), so for simplicity let's assume this is not a problem.

2) Problem of multiple bases, and I want to avoid virtual inheritance via virtual bases (performance penalty). This is probably solvable by using directives inside the Feature specializations.

Still I am not 100% sure I can make this work and it will not scale well for a large number of features. In fact the number of elements composing the hierarchi is given by the binomial coefficient, almost factorial: F - > F (1) F,G - > FG, F, G (3)
F,G,H -> FGH, FG, GH, FH, F, G, H (7)

I would like to know if there is a solution to the design problem which involves the following conditions:

1) The code should have a runtime performance equivalent to Example 1.

2) I want to be able to specify easily some set of features and being able to "pass in" any pointers to objects that have this (and usually extra) functionality.

3) I want code that depends on features f() and g() not to require re-compiling whenever I consider a new feature h() somewhere else.

4) I do not want to template everything that want to use such features (almost all the code). There should be some kind of "separation", see point 3.

Looking in the (numerical) libraries I have usually found the two approaches:

1) Define a huge abstract base class B that have f(),g(),h(),...... Problems: whenever I want to add a new feature z(), B has to be modified, everything needs to be re-compiled (even if this code does not care about z() at all), all the existing implementations D1, D2,... of B needs to be modified (usually by having them throw an exception for z() apart for the new implementation that supports z()). The solution of enlarging B progressively when I need to add features is not a good one for the problem at hand, as the featurs f() and g() are really "as important" as h() and i(), an neither is "more basic" then the others.

2) Separate all the functionalities and use one pointer for each functionality. However, this is cumbersome for the user (in most situations 4 or more pointers would have to be carried around), and for the problem at hand this approach is not optimal (here really it is 1 object that may or may not do something, in fact calling f() will modify the result obtained by g() and vice-versa).

Thank you in advance for your help.

KRao.

A: 

I would go with a hand-written approach to your Example 3. When you look through your code, you will find that you don't use every combination of features possible; instead you use a small subset. Instead of having templates generate all possible combinations, just write the ones you need. Say you have F, G, and H, and you use FGH, FG, GH, and F, G, H individually.

template <typename T1, typename T2 = N, typename T3 = N> struct Feature; 
template <typename T1> struct Feature<T1,N,N> : T1 { };
template <> struct Feature<F,G,N> : Feature<F>, Feature<G> { };
template <> struct Feature<G,H,N> : Feature<G>, Feature<H> { };
template <> struct Feature<F,G,H> : Feature<F,G>, Feature<G,H> { };

If you leave out some combination that you need, the compiler will tell you.

Hyman Rosen
Would not this still present the problem that in Feature<F,G,H> G is inherited twice? Moreover if I do need to introduce a new features combination, I need to introduce a new template specialization and then essentially all the code needs to be recompiled even for part of the code which do not use this specific new combination.
KRao
Probably should use virtual inheritance to avoid duplicate bases.
jon hanson
A: 

In this particular case in my opinion terse code makes more harm than good. Why not pass two (or three) interface pointers in the constructor or function?

class F
{ 
public:
   virtual double f(double x) =0;
};

class G
{
public:
    virtual double g(double x) = 0;
};

class H
{
public:
    virtual double h(double x) = 0;
};

class FG : public F, public G
{
public:
    double f(double x) {return 1.;}
    double g(double x) {return 2.;}
};

class FGH : public F, public G, public H
{
public:
    double f(double x) {return 1.;}
    double g(double x) {return 2.;}
    double h(double x) {return 3.;}
};

void Foo(F* pF, G* pG)
{
    pF->f(5.);
    pG->g(10.);
}

void Foo2(F* pF, G* pG, H* pH)
{
    pF->f(5.);
    pG->g(10.);
    pH->h(20.);
}

int main()
{
    FGH fgh;
    Foo(&fgh, &fgh);
    Foo2(&fgh, &fgh, &fgh);
}
BostonLogan
Thank you for your reply! I went with this approach in a previous version of the library, but it turned out (for the specific numerical problem at hand, this is the numerical part of the library that deals with generation of random variables from different distributions) to be so much unfriendly that I decided to develop a new interface. Of the two approaches I mention at the end of my question the approach 1 is preferrable.
KRao
+1  A: 

Now that's an interesting problem!

The obvious (and simple) solution would be to go template crazy. If every single function only required an interface (and not a definite type), this would alleviate most of your concerns. But of course this has a penalty of its own in term of dependency and you reject this solution in point 4).

Dynamic casting ?

struct Feature { virtual ~Feature() {} };

class F: public Feature {};
class G: public Feature {};

Now a class which implements F and G is declared like that

class Impl: public F, public G {};

And a method requiring F and G like this

void method(Feature const& i)
{
  F const& myF = dynamic_cast<F const&>(i);
  G const& myG = dynamic_cast<G const&>(i);

  myF.f(2.0);
  myG.g(2.0);
}

Admittedly there may be a slight performance penalty and the type-safety is not checked at compile-time.

Template Forwarding

This however entails another solution:

namespace detail
{
  void methodImpl(F const& f, G const& g);
}

template <class T>
void method(T const& t)
{
  detail::methodImpl(t,t);
}

This combines the approach of BostonLogan with a better interface. No user code should ever mention the detail namespace (which is easy to test) and if so you are guaranteed that no one use two different objects to invoke methodImpl.

This seems to cover most of your needs:

  1. The runtime performance is equivalent to your example
  2. T might be inheriting from H or Z, you don't care, as long as it inherits from F and G the code will compile
  3. Adding another Feature does not change anything
  4. ... well you have to template the interface (method), but this is a one-liner forwarder to a non-template method which does the actual heavy-lifting.

The only thing that annoys me there is that methodImpl actually have 2 references to the same object, which might entails problems for the future.

Getting rid of multiple references

It's not going to be easy, but we are going to wrap this object and delegate the work.

The idea is that from an object of an unknown type inheriting from a given set of functionality, we can create an object of a known type that forwards all operations to the first object (which it takes in its constructor).

To achieve this we need 2 more things though:

  • Each Feature should declare a forwarder
  • We need a system of forwarder aggregation

Let's cover the first point:

class F
{
public:
  void f(double d);
  void f2(double d, double e) const;
};

class FForwarder
{
public:
  FForwarder(F& f) : m_object(f) {}

  void f(double d) { m_object.f(d); }
  void f2(double d, double e) const { m_object.f(d,e); }

private:
  F& m_object;
};

Easy enough, but cumbersome.

Let's cover the aggregation:

struct nil {};

template <class Head, class Tail>
struct Aggregator: Head, Aggregator<Tail::head, Tail::tail>
{
  typedef Head head;
  typedef Tail tail;

  template <class T>
  Aggregator(T& t) : Head(t), Aggregator<Tail::head, Tail::tail>(t) {}
};

template <class Head>
struct Aggregator<Head,nil> : Head
{
  typedef Head head;
  typedef nil tail;

  template <class T>
  Aggregator(T& t) : Head(t) {}
};

template <>
struct Aggregator<nil,nil>
{
  typedef nil head;
  typedef nil tail;

  template <class T>
  Aggregator(T&) {}
};

And now, on to the usage.

Either by putting the burden on the caller:

int method(Aggregator<FForwarder, Aggregator<GForwarder, HForwarder> >& fgh);

Or by writing a template forwarder:

namespace detail
{
  typedef Aggregator<FForwarder, Aggregator<GForwarder, HForwarder> > methodImplArg;
  int methodImpl(methodImplArg& arg);
}

template <class T>
int method(T& t)
{
  detail::methodImplArg arg = detail::methodImplArg(t);
    //named temporary because it is passed by reference to non-const

  return detail::methodImpl(arg);
    // forward the result as well
};

This addresses the problem of methodImpl being passed 2 arguments rather neatly, though it does require extra work... I suppose there should be a simpler way but could not pinpoint it yet.

Matthieu M.
I really do appreciate your extensive reply and your effort. Using your nation, the only problem that I see with the template implementations is that method has to be templetized too in order to be able to accept a generic T (which supports say F and G). I was trying to avoid this: I would like method to "specify" that it expects something that provides F and G and be compiled once and for all. I should have made this clearer in Requirement 3, I apologize.I am now considering to use a container like boost::fusion::map to store one pointer for each required feature. Will keep you updated.
KRao
A: 

I solved this problem with the following code. I made the classes F,G,H,N non-pure abstract just to simplify the exposition but this can be easly changed (use F as tag, Feature for the interface).

Features:

class F {
public:
 virtual double f( double x )
 {
  return 10.0;
 };

 virtual double sample( const F& t , double x )
 {
  return f( x );
 };
};

class G {
public:
 virtual double g( double x )
 {
  return 10.0;
 };

 virtual double sample( const G& t , double x )
 {
  return g( x );
 };

};

class H {
public:
 virtual double h( double x )
 {
  return 10.0;
 };

 virtual double sample( const H& t , double x )
 {
  return h( x );
 };
};

class N {};

I use the boost::fusion map class, but with some effort this can be re-written to avoid this.

The Impl class allows to define functions that simplify the user interface. This is needed because I am using composition/aggregation instead of inheritance and thus the class which implements the required feature must be "selected manually".

template<class Derived, class T>
class Impl
{
 template<class S1, class S2>
 double sample( const S2& x )
 {
  return boost::fusion::at_key<S1>( static_cast<Derived*>(this)->container )->sample( S1() , x );
 }
};

template<class Derived>
class Impl<Derived,F>
{
public:

 double f( double x )
 {
  return boost::fusion::at_key<F>( static_cast<Derived*>( this )->container )->f( x );
 }
};

This is the key class that "aggregates" features T1,T2,T3. Below same class for features T1,T2. Class for T1 only left as user exercise :)

template<class T1, class T2=N, class T3=N>
class Rvg : public Impl<Rvg<T1,T2,T3>,T1>, public Impl<Rvg<T1,T2,T3>,T2>, public Impl<Rvg<T1,T2,T3>,T3>
{
public:

 typedef typename boost::fusion::map<
  boost::fusion::pair<T1,T1*>,
  boost::fusion::pair<T2,T2*>,
  boost::fusion::pair<T3,T3*> > container_type;

 container_type container;

public:

 Rvg()
 {}

 template<class S1>
 Rvg( S1* s1Ptr )
 :
 container( static_cast<T1*>( s1Ptr ), static_cast<T2*>( s1Ptr ), static_cast<T3*>( s1Ptr ) )
 {}

 template<class S1, class S2, class S3>
 Rvg<T1,T2,T3>& operator = ( const Rvg<S1,S2,S3>& rhs )
 {
  container = rhs.container;
  return *this;
 }

 template<class S>
 S& operator() ( S& s )
 {
  return *boost::fusion::at_key<S>( container );
 }

};

template<class T1, class T2>
class Rvg<T1,T2,N> : public Impl<Rvg<T1,T2,N>,T1>, public Impl<Rvg<T1,T2,N>,T2>
{
public:

 typedef typename boost::fusion::map<
  boost::fusion::pair<T1,T1*>,
  boost::fusion::pair<T2,T2*> > container_type;

 container_type container;

public:

 Rvg()
 {}

 template<class S1>
 Rvg( S1* s1Ptr )
 :
 container( static_cast<T1*>( s1Ptr ), static_cast<T2*>( s1Ptr ) )
 {}

 template<class S1, class S2, class S3>
 Rvg& operator = ( const Rvg<S1,S2,S3>& rhs )
 {
  container = rhs.container;
  return *this;
 }

 template<class S>
 S& operator() ( S& s )
 {
  return *boost::fusion::at_key<S>( container );
 }

};

//Rvg<T1,N,N> defined according to the same pattern.

Examples of class that supports features F,G,H.

class SuppFandGandH : public F , public G , public H
{
public:
 double f( double x ) { return 14.0; }
 double g( double x ) { return 15.0; }
 double h( double x ) { return 16.0; }

 double sample( const F& f , double x ) { return 17.0; }
 double sample( const G& g , double x ) { return 18.0; }
 double sample( const H& h , double x ) { return 19.0; }
};

Example of user use:

int main()
{
 SuppFandGandH suppFandGandH;

 Rvg<F,G,H> myRvgFGH( &suppFandGandH ); //Automatic construction

 myRvgFGH( F() ).f( 0.0 ); //Base calling
 myRvgFGH.sample<F>( 0.0 ); //Fwded calling
 myRvgFGH.f( 0.0 ); //Specific fwded calling

 myRvgFGH.sample<F>( 0.0 );
 myRvgFGH.sample<G>( 0.0 );
 myRvgFGH.sample<H>( 0.0 );

 Rvg<F,G> myRvgFG;
 myRvgFG = myRvgFGH; //Less from More

 Rvg<F,G> myRvgFG2( &suppFandGandH ); //Again automatic construction
 Rvg<F,H> myRvgFH( &suppFandGandH ); //The same
}

This should satisfy all the four requirements.

KRao