views:

87

answers:

1

As my prveious question sounded confusing, I think it's best to clearly state what I want to achieve.

I have (ignore the inheritance for now and focus on X):

class Base {};

class X : public Base {
private:
    double m_double;
public:
    template<class A> friend 
    void state( A& a, const X& x ) {
        data( a, x.m_double, "m_double" );
    }   
};

I can now introduce arbitrary new classes that performs different actions based on how the data function is overloaded, we will refer to these as Accessors in the following:

class XmlArchive {...}; //One Accessor
template<class T>
void data( XmlArchive& a, const T& t, const std::string& str ) {
//read data and serialize it to xml Archive
}

class ParameterList {...}; //Another Accessor
template<class T>
void data( ParameterList& a, const T& t, const std::string& str ) {
//put data in a parameter list 
}

I can then write:

X myX;

XmlArchive myArchive;
state( myArchive, myX );

ParameterList myParameters;
state( myParameters, myX );

Fantastic, code reuse! :D However the following (clearly) fails:

Base* basePtr = new X; //This would come from factory really, I should not know the type X
state( myParameters, *basePtr ); //Error

The goal is to make this last call succed. What I have considered (and why it is not acceptable):

First option: make all Accessors inherit from a common base class, say AccessorBase, then write in Base

virtual state( AccessorBase& a ) const = 0;

and implement the required code in X (the syntax to call state will be marginally different but this can be fixed). The problem is that AccessorBase will need to have a virtual function for every possible type which comes as second argument in the data function(s). As these types can be user-defined classes (see case of class composition, X which has Y as data member) I do not see how to make this strategy can work.

Second option: create a virtual function in Base for every Accessor. This violates the open/close principle as adding a new Accessor class (say TxtArchive) will require modification of the base class.

I understand why virtual member function cannot be templated (possible different vtbls in different compilation units). However it seemes to me that there should be a solution to this problem... Base knows that it really is of type X, and the type of the Accessor is always explicit, so it is a matter of finding a way of calling (for Accessor of type XmlArchive):

state( xmlArchive, x ); //xmlArchive of type XmlArchive, x of type X

which will yield the result.

To sum-up I would like the call:

state( myParameters, *basePtr );

to succeed if basePtr is pointing to a derived class with a function template compatible with the call and to throw an exception otherwise.

It seemes to me that boost::serialize does something similar but I cannot figure out how (it may be it is re-implemnting inheritance relationships in C++ via templates, I see a This() funcion returning the most derived pointer but it gets really confusing...)

Thank you again in advance for your help!

A: 

I believe you can do this using dynamic_cast or a C-style cast, as long as you know you have an object of X, cast Base* to X* and call your method (which should be static in your example.) If you're inheritance chain is deeper and you don't know if you have X, Y, or Z at compile time then you can still do it, but you'll need to enable RTTI.

So to sum up:

X::state( myParameters, *(X*)(basePtr) );

Or if you have RTTI enabled:

X::state( myParameters, *dynamic_cast<X*>(basePtr) );

In the X, Y, Z scenario, you'll need three branches wth Z::state, Y::state, and X::state, calling the right one depending on the run time type of basePtr.

switch(basePtr->get_type())
{
    case TYPE_X:
        X::state( myParameters, *(X*)(basePtr) );
        break;
    case TYPE_Y:
        Y::state( ... );
        break;
}

You get the idea.

Eloff
But as I stated in the post I would know know that I have X, so the responsability for calling the correct function should be delegated to the derived class themselves (X Y and Z, or anything else inheriting from Base), which knows about its self-type.
KRao
Yes, that's why I say you're going to need RTTI (or some enum field in Base that stores the type X, Y, Z) because you're going to need to take different branches depending on the _runtime_ type of the object. You can't use virtual functions, which leaves you only with some form of RTTI, or possibly emulating a virtual function with functors. Either way you or the compiler has to add something to Base.
Eloff