开发者

C++ member function template in derived class, how to call it from base class?

开发者 https://www.devze.com 2023-01-01 07:11 出处:网络
As my prveious question sounded confusing, I think it\'s best to clearly state what I want to achieve.

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开发者_如何学JAVA 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!


You could use the vistor pattern for this:

class IVisitor
{
  public:
    virtual void on_data( double, const char * )=0;
    virtual void on_data( std::string, const char * )=0;
}; 

class Base 
{
  public:
    virtual void visit( IVisitor * v )=0;
};

class X : public Base 
{
  private:
    double m_double;
    std::string m_string;
  public:
    void visit( IVisitor * v)
    {
        v->on_data( m_double, "m_double" );
        v->on_data( m_string, "m_string" );
    }   
};

Base * base = ...;
XmlArchive ar;  // This must derive from IVisitor
base->visit(&ar);
ParameterList pl; // This must derive from IVisitor
base->visit(pl);

If you dislike the need to implement each of the different on_data types in the IVisitor, you can use a boost::any - but in my experience the branching required in the implementations of that function are not worth it.


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.


Not sure I fully understand the parameters of what you're asking, but my spider-sense is signalling that what you're after is an application of the Curiously Recurring Template Pattern:

class StateBase {
  virtual void state() = 0;
};

template<typename T>
class StateMixin : public StateBase {
  void state() {
    T::data();
  }
};

class MyType : public StateMixin<MyType>
{
  void data() {};
}

Here, state() is defined with StateMixin and is callable from any StateBase instance, but is separately instantiated by the compiler for each user-defined type implementing data.


Add the friend declaration to your Base so it became

class Base 
{
   template<class A> friend void state( A& a, const X& x );
};
0

精彩评论

暂无评论...
验证码 换一张
取 消