Sign in to follow this  

Templates and inheritance

This topic is 4034 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi all. Is there any way to accomplish this in C++:
struct Base
{
	template <class T> void doIt(T& data)
	{
		// ..Some magic code that calls myDoIt for the correct class
	}
};

struct Foo : public Base
{
	template <class T> void myDoIt(T& data)
	{
		printf("Bytes %d\n", sizeof(data));
	}
};

struct Bar : public Base
{
	template <class T> void myDoIt(T& data)
	{
		printf("Bits %d\n", sizeof(data) << 3);
	}
};

void
main()
{
	Base* foo = new Foo;
	Base* bar = new Bar;
	int test;
	foo->doIt(test); // prints "Bytes 4"
	bar->doIt(test); // prints "Bits 32"
}





I think I have a working solution but it requires a lot of code and isn't very clean. Any hints? Eidt: What I'm looking for is some mechanism to enable templated virtual class methods. [Edited by - eq on November 28, 2006 7:25:18 AM]

Share this post


Link to post
Share on other sites
Templates is a compile-time mechanism and polymorphism is a run-time mechanism. As far as I know, to combine them you'll either have to decide template function during run-time, or decide deriving class during run-time and use some function table. In your specific case it doesn't seem like you could directly use void-pointers as the data size has to be known, maybe send as parameter? I'm not sure you can combine them in another way, it would be interesting to see some "magic" code for that.

Here's however some code that is, "not so magic" [grin]
It decides the class type using an id. and uses a switch-statement to let the called template functions be compiled.


struct Base
{
enum Classes {BASE = 0, FOO, BAR};

int id;

Base() {id = BASE;}

template <class T> void doIt(T& data)
{
// ..Some magic code that calls myDoIt for the correct class
switch (id)
{
case FOO:
((Foo*) this)->myDoIt(data);
break;
case BAR:
((Bar*) this)->myDoIt(data);
break;
}
}
};

struct Foo : public Base
{
Foo() {id = FOO;}

template <class T> void myDoIt(T& data)
{
printf("Bytes %d\n", sizeof(data));
}
};

struct Bar : public Base
{
Bar() {id = BAR;}

template <class T> void myDoIt(T& data)
{
printf("Bits %d\n", sizeof(data) << 3);
}
};

void
main()
{
Base* foo = new Foo;
Base* bar = new Bar;
int test;
foo->doIt(test); // prints "Bytes 4"
bar->doIt(test); // prints "Bits 32"
}


Share this post


Link to post
Share on other sites
Whenever you find yourself writing...

if (object == this_type)
// Do this
else if (object == that_type)
// Do this


...type code, one is probably better off using the built in RTTI, or better yet finding a different solution to the problem. At the very least, have:

else
{
assert (0); // Unknown type
}

// Or with a switch
default:
assert (0); // Unknown type
break;


The main problem with the code presented by Dim_Yimma_H is it's a maintenance nightmare. If you derive a class NewFoo from Base, and forget to update the enumeration and the switch statement, you could have a problem. At best, the assert will warn you. Or it could just fail silently without you knowing. Or it could lead to a crash.

A different solution to the problem as it was presented:

struct Base
{
template <class T> void doIt (T& data)
{
printf ("Bytes %d\n", sizeof_Modifier (sizeof (T)));
}

protected:

virtual size_t sizeof_Modifier (size_t initial_size) = 0;
};

struct Foo : public Base
{
size_t sizeof_Modifier (size_t initial_size)
{
return initial_size;
}
};

struct Bar : public Base
{
size_t sizeof_Modifier (size_t initial_size)
{
return initial_size << 3;
}
};


Share this post


Link to post
Share on other sites
Quote:
A different solution to the problem as it was presented:

Unfortunatly my real problem is a little bit more involved.

As far as I can tell there are only two ways to do this:
1. Let the base class know of all inherited classes á la Dim_Yimma_H.
2. Let the base class know of all possible processing of T's á la Ro_Akira.

In my case there is no way that I can use option one, but I might get a way with option two, albeit not in a clean way.

Thanks for the input guys.

One straight forward way that is similar to option two (letting the base class know about all possible T's):


#define DeclDoIt(T) virtual void doIt(T& data) = 0
#define ImplDoIt(T) virtual void doIt(T& data) { myDoIt(data); }

struct Base
{
DeclDoIt(int);
DeclDoIt(float);
DeclDoIt(double);
DeclDoIt(char);
};

struct Foo : public Base
{
ImplDoIt(int);
ImplDoIt(float);
ImplDoIt(double);
ImplDoIt(char);
template <class T> void myDoIt(T& data)
{
printf("Bytes %d\n", sizeof(data));
}
};

struct Bar : public Base
{
ImplDoIt(int);
ImplDoIt(float);
ImplDoIt(double);
ImplDoIt(char);
template <class T> void myDoIt(T& data)
{
printf("Bits %d\n", sizeof(data) << 3);
}
};

void
main()
{
Base* foo = new Foo;
Base* bar = new Bar;
int test;
foo->doIt(test); // prints "Bytes 4"
bar->doIt(test); // prints "Bits 32"
}





Still get's you into trouble if you need to add support for a new type (needs to add the correct Decl macros into all inherited classes).

Share this post


Link to post
Share on other sites
Quote:
Unfortunately my real problem is a little bit more involved.

I was hoping you wouldn't say that. But I suspected ;)

Quote:
Still get's you into trouble if you need to add support for a new type (needs to add the correct Decl macros into all inherited classes).

Okay, but correct me if I'm wrong, the compiler will decline to compile your code with something similar to 'cannot instantiate abstract class' due to doIt(new_type) being abstract, should it not? Compile time errors are a win over potential run-time errors.

It might be a bit too much, but if you're going down the macro route, you could also do this:

// You might want to put each DeclDoIt on a separate line - I'd do it but GDev.net is making it hard
#define DeclBase DeclDoIt(int); DeclDoIt(float); DeclDoIt(double); DeclDoIt(char);

#define ImplBase ImplDoIt(int); ImplDoIt(float); ImplDoIt(double); ImplDoIt(char);
struct Base
{
DeclBase
};

struct Foo : public Base
{
ImplBase
}





This would save on updating the headers of all inheriting classes when a new type arrives. You'll still get linking errors when a new type arrives and you try to use it with a class that hasn't defined an implementation for it.

P.S. Just in case it's not a typo: I'd kindly suggest you get out of the habit of 'void' main. See this.

Share this post


Link to post
Share on other sites
Virtual template class methods are difficult to get working, this is a method ive used before, it looks ugly but it is relatively easy to maintain and the end-user of the classes is nearly transparent to its operation - exceptions are they must keep in mind that 'doIt' and 'myDoIt' are only virtual methods when immeadiately derived from BaseCRTP and they must also pass the derived class to BaseCRTP as a template parameter.

I think really the only attractions to this method are the simplicity in foo and bar, which is useful if the classes are designed to be extensible. Also that with meta-template trickery this method can be useful for performing various pieces of useful compile-time processing.

Is this a potentially viable option for you?:


class BasePolymorphic
{
protected:
//Hidden virtual calls to each data type
virtual void doItImpl(int data) = 0;
virtual void doItImpl(float data) = 0;
virtual void doItImpl(double data) = 0;
virtual void doItImpl(char data) = 0;

public:
template <class T> void doIt(T& data)
{
//Delegates to the virtual methods
doItImpl(data);
}
};

//Implements the 'Curiously Recurring Template Pattern - CRTP
template <class Derived>
class BaseCRTP : public BasePolymorphic
{
//Hidden virtual methods call the templated member
virtual void doItImpl(int data) { doIt(data); }
virtual void doItImpl(float data) { doIt(data); }
virtual void doItImpl(double data) { doIt(data); }
virtual void doItImpl(char data) { doIt(data); }

//Used to access the derived class
Derived* This() { return static_cast<Derived*>(this); }

public:
template <class T> void doIt(T& data)
{
// ..Some magic code that calls myDoIt for the correct class
This()->myDoIt(data);
}
};

struct Foo : public BaseCRTP<Foo>
{
template <class T> void myDoIt(T& data)
{
printf("Bytes %d\n", sizeof(data));
}
};

struct Bar : public BaseCRTP<Bar>
{
template <class T> void myDoIt(T& data)
{
printf("Bits %d\n", sizeof(data) << 3);
}
};

void
main()
{
BasePolymorphic* foo = new Foo;
BasePolymorphic* bar = new Bar;
int test;
foo->doIt(test); // prints "Bytes 4"
bar->doIt(test); // prints "Bits 32"
delete foo;
delete bar;
}




This method uses the curiously recurring template pattern to make it easy for foo and bar or any other derived classes to inherit a virtual templated class member. There is also a runtime polymorphic base class that can be used as a pointer or reference for runtime polymorphism.

Note that both the base classes require a long list of virtual methods for each data type they can handle but that these virtual functions need not be present in every derived class.

Sorry im a bit rushed, the code above compiled for me, any questions just ask.

Dave

Share this post


Link to post
Share on other sites
Quote:
I was hoping you wouldn't say that. But I suspected ;)

In my case the derived classes are defined in separate DLL's, furthermore the base class is pure virtual, hence it's very hard (if not impossible) to let the base know about ALL derived classes.
The DLL's used may also vary depending on the system it's beeing used on...

Quote:
Compile time errors are a win over potential run-time errors.

Totally agree there!
But in my case (since it's DLL's) it wouldn't fail until I load a DLL that hasn't been recompiled (not a major issue though, but still not a very nice solution).

Quote:
It might be a bit too much, but if you're going down the macro route, you could also do this:

Yepp that would clean it up a bit... however I really don't like macros for code since it's virtually impossible to do some serious debugging.

dmatter: Gonna get my head around that code and see what I can make of it (it's actually a pattern that I use quite frequently, never knew it had a name though ;))

Share this post


Link to post
Share on other sites

This topic is 4034 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

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