Sign in to follow this  
MikeJM

C++ Template Inheritance Question

Recommended Posts

MikeJM    171
There is a lot of template intricacies I don't understand so hopefully someone can help me out. What I want to do is:
class A
{
public:
	A( int &num ) : _num(num) {}
private:
	int &_num;
};

template< typename T, typename Base >
class Glue : public Base
{
public:
	Glue() {}
};

int g_num = 0;

class B : public Glue<B,A>
{
public:
	B() : A(g_num) {}
};
This is obviously a contrived exmaple but assume that Glue has some useful functionality to automate a lot of stuff in a type-safe way. The compiler can't construct A through Glue because no default constructor exists but it will try anyways and come up with a linker error if you remove the explict call to A's constructor. And the compiler won't allow the A contstructor in class B's constructor because it will then be trying to construct A twice. So what I was thinking what I needed was to add a constructor to class Glue<B,A> that looks like Glue<B,A>::Glue( int &num ) : A(num) {} So in trying to do this I modified the code to be:
class A
{
public:
	A( int &num ) : _num(num) {}
private:
	int &_num;
};

template< typename T, typename Base >
class Glue : public Base
{
public:
	Glue() {}

	int GlueFunction() const { return 3; }
};

class B;

template<>
class Glue<B,A> : public A
{
public:
	Glue( int &num ) : A(num) {}
};

int g_num = 0;

class B : public Glue<B,A>
{
public:
	B() : Glue<B,A>(g_num) { int blah = GlueFunction(); }
};

But in doing so C++ assumes I want to fully specialize the entire class even though I just want to insert one function. Thus doing this requires copy and pasting all the code from Glue for any Glue where Base doesn't have a default constructor. The other solution I thought of was to go to:
class A
{
public:
	A( int &num ) : _num(num) {}
private:
	int &_num;
};

template< typename T, typename Base >
class Glue
{
public:
	typedef Base Super;

	Glue() {}

	int GlueFunction() const { return 3; }
};

int g_num = 0;

class B : public A, public Glue<B,A>
{
public:
	B() : A(g_num) { int blah = GlueFunction(); }
};
But this adds an extra step for things to go wrong, requires a change to a large number of existing classes, and the engineers here, myself included, like to stay away from multiple inheritence as it tends to complicate things.

Share this post


Link to post
Share on other sites
xEricx    572
Just a quick question... who's the owner of A? Should B instanciate it or is it the responsibility of the glue layer? Or, maybe the A is totally external and can be applied on B?

With a more concrete example it might be easier to help you. Anyway, who ever is the owner of A should know how to construct an A.

Stuff like having (typing it in the msg window, probably wont compile...)


class A
{
public:
typedef int InternalType;
A(InternalType& in_IT);
}

template< typename T, typename Base > class Glue : public Base
{
public:
Glue(Base::InternalType& in_IT) : Base(in_IT) { }
}




Now, glue's constructor depends on what's necessary to build an A, but this means B has to know what to pass to the glue layer to instanciate an A.

I hope that made sense hehe

Share this post


Link to post
Share on other sites
Edit: What did I do :o Mods fix please I am sorry :(

[quote]Original post by MikeJM

template< typename T, typename Base >
class Glue : public Base
{
public:
Glue() {}
};

int g_num = 0;

class B : public Glue<B,A>
{
public:
B() : A(g_num) {}
};





[/quote]
First off, are you sure you want the Glue instantiation to be a public base? That is to say, is a "B" logically a kind of Glue instantiation? It probably is not, in which case don't do this at all and favor a different approach.

If you do in fact want the Glue instantiation to be a public base, keep in mind that you can only pass constructor arguments to direct bases from a derived class (well, and virtual bases, but don't go making A a virtual base just to pass constructor arguments). In other words, B cannot pass any arguments directly to A -- you have to pass them to the instantiation of Glue which then passes them to A. So, instantiations of your Glue template must have constructors which are able to forward arguments to its base. In order to get the generic functionality you want and allow Glue to have any base you throw at it, you need to make N template constructors for Glue with each one taking a different amount of parameters by reference and which just pass their arguments to the base, where N is the maximum number of parameters a Base would conceivably have for its constructor.

In other words you need to do:


Glue() {}

template< typename Param0 >
Glue( Param0& arg0 ) : a( arg0 ) {}

template< typename Param0, typename Param1 >
Glue( Param0& arg0, Param1& arg1 ) : a( arg0, arg1 ) {}

// etc. for however many constructor parameters you wish to support






Tedious yes, though if you have Boost installed, you can make your life simpler by using Boost.Preprocessor to generate the constructors automatically.


#include <boost/preprocessor/repetition/enum_params.hpp>
#include <boost/preprocessor/repetition/enum_binary_params.hpp>
#include <boost/preprocessor/repetition/repeat_from_to.hpp>
#include <boost/preprocessor/arithmetic/inc.hpp>

// Change this value to get a varying number of maximum params
#define GLUE_MAX_CONSTRUCTOR_PARAMS 10

template< typename T, typename Base >
class Glue : public Base
{
public:
Glue() {}

#define GLUE_CONSTRUCTOR( z, num_params, dummy ) \
template< BOOST_PP_ENUM_PARAMS_Z( z, num_params, typename Param ) > \
Glue( BOOST_PP_ENUM_BINARY_PARAMS_Z( z, num_params, Param, & arg ) )\
: Base( BOOST_PP_ENUM_PARAMS_Z( z, num_params, arg ) ) \
{ \
}

BOOST_PP_REPEAT_FROM_TO( 1
, BOOST_PP_INC( GLUE_MAX_CONSTRUCTOR_PARAMS )
, GLUE_CONSTRUCTOR
, BOOST_PP_NIL
)

#undef GLUE_CONSTRUCTOR
};











Quote:
Original post by MikeJM

class B : public A, public Glue<B,A>
{
public:
B() : A(g_num) { int blah = GlueFunction(); }
};


But this adds an extra step for things to go wrong, requires a change to a large number of existing classes, and the engineers here, myself included, like to stay away from multiple inheritence as it tends to complicate things.


Before going any further, do Glue instantions need to have any knowledge of that second template argument at all when doing things this way? If not, just get rid of the second template parameter for Glue entirely. As well, with this approach, if A is logically a public base whereas the Glue instantiation is not, the best approach you can take is using multiple inheritance as you are doing right now, but make the Glue instantiation a private base as opposed to a public one, and then promote the functions you want from the Glue instantiation to be public in B with using declarations to make them accessible from outside of the class:


class B : public A, private Glue<B,A>
{
public:
B() : A(g_num) { int blah = GlueFunction(); }
public:
// Makes GlueFunction public in B
using Glue<B,A>::GlueFunction;
};


This approach allows you to have Glue instantations generate common functions but avoids a nasty is-a relationship and makes the base an implementation detail.

Share this post


Link to post
Share on other sites
MikeJM    171
It seems I have oversimplified the problem. I'll be a bit more specific. The Glue class described here is an extension of CRTTIClass found in Game Programming Gems 5 section 1.4 "Using Templates for Reflection in C++" by Dominic Filion.

It provides some core functionality to support network updates, persisting data to files, script data and function binding, and run-time type information easily. A third class C for example would derive from B thusly:

class C : public Glue<C,B>
{
public:
C() {}
};


Publically inheriting from Glue makes it easier to expose those interfaces.

The problem with MI is that the compiler then doesn't know which version of the Glue functions to resolve to. It could be the version in B::Glue<B,A> or the version in Glue<B,A> since both are now at the same level in the inheritence chain.

Glue is never used directly its sole purpose is to insert functions and members into classes easily so for now I've given up on the template method and instead using:

#define SUPPORT_GLUE(T,BASE) ...
#define DEFINE_GLUE(T) ...

class B : public A
{
SUPPORT_GLUE(B,A);
public:
...
};

// cpp file
DEFINE_GLUE(B);



P.S.
I didn't know you could do:
// Makes GlueFunction public in B
using Glue<B,A>::GlueFunction;


That is really cool.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this