Sign in to follow this  
Trillian

virtual templated member function workaround

Recommended Posts

Hello! I am currently working on an IO system with a class hierarchy that looks like so : class IO; class IOReader : public IO; class IOWriter : public IO; class Buffer : public IOReader, public IOWriter; class FileStream : public IOReader, public IOWriter; I'd like to be able to use my IOReader objects like this : Vector2D v = myioobject.readVector2D(); The only problem I have is that I have LOTS of read*** like this. However, I got a "wonderful" idea :
class IOReader : public IO
	{
	public:
		// Reading operations
		template<typename T> virtual void readType(T & v) = 0;
		template<typename T> virtual T readType() = 0;
		template<typename T> virtual void readType(T * v, int num) = 0;
		//virtual template<typename T> void readBuffer(Buffer & buf, long numelem) = 0;
		
		virtual String readString(char delim = '\0', int presumedlength = 1) = 0;
		virtual String readString(long num) = 0;

		// Predefined reads
		inline float  readFloat()  { return readType<float>();  }
		inline double readDouble() { return readType<double>(); }
		inline long   readLong()   { return readType<long>();   }
		inline short  readShort()  { return readType<short>();  }
		inline char   readChar()   { return readType<char>();   }

		inline Vector2D  readVector2D()             { return readType<Vector2D>();  }
		inline Vector3D  readVector3D()             { return readType<Vector3D>();  }
		inline void      readMatrix2D(Matrix2D & m) { return readType<Matrix2D>(m); }
		inline void      readMatrix3D(Matrix3D & m) { return readType<Matrix3D>(m); }
		inline Rect2D    readRect2D()               { return readType<Rect2D>();    }
		inline TexRect2D readTexRect2D()            { return readType<TexRect2D>(); }
		inline Color4D   readColor4D()              { return readType<Color4D>();   }
	};


like this, I only had to override the templated functions without having to rewrite all the read*** helpers. However, there is a problem : it won't compile! I was horrified of learning that I couldn't have virtual member functions which were using templates! So gamedev.net is my last chance of finding a simple way to do somehting equivalent, if it fails, i'll go and put each and every of my read*** helper functions purely virtual and have no templates. Please help me!

Share this post


Link to post
Share on other sites
You could do what the iostream library does: define a few operator overloads for basic types and then extend by adding additional operator overloads that you implement in terms of those operators.

Share this post


Link to post
Share on other sites
Thats not crazy! I should've taught about it ^-^!

So in my example, i'd have :

virtual float readFloat() = 0;
virtual double readDouble() = 0;
virtual long readLong() = 0;
virtual short readShort() = 0;
virtual char readChar() = 0;

and all the others inlined into IOReader?
something like this :

inline Vector3D readVector3D()
{
return Vector3D(readFloat(), readFloat(), readFloat());
}

Yes, it seems to be perfect!

Share this post


Link to post
Share on other sites
What compiler are you using?
I just tested these two examples with GCC/G++ 4.02 on linux.
I compiled it with the c++98 standard and it worked fine.

Here is the output for the second example:
Quote:

~> g++ -o template template.cpp -std=c++98
~> ./template
test1
~>



1 #include <iostream>
2 using namespace std;
3
4 class baseclass
5 {
6 public:
7 virtual void does()=0;
8 };
9 template< class T> class someclass : public baseclass
10 {
11 public:
12 void does(){ T s; cout<<"test"<<s<<endl;};
13 };
14
15 int main(int argc,char*argv[])
16 {
17 baseclass *l = new someclass<int>();
18 l->does();
19 return 0;
20 }




1 #include <iostream>
2 using namespace std;
3
4 template< class T> class someclass
5 {
6 public:
7 virtual void does(T s){ ; cout<<"test"<<s<<endl;};
8 };
9
10 int main(int argc,char*argv[])
11 {
12 someclass<int> l;
13 l.does(1);
14 return 0;
15 }

Share this post


Link to post
Share on other sites
Template classes can have non-template virtual functions, but no class can have a templated virtual function.

Share this post


Link to post
Share on other sites

template<typename T>
class CanRead {
public:
virtual void DoRead(T & v) = 0;
virtual void DoReads(T * v, int num) = 0;
};

template<typename T>
T GetRead( CanRead<T>* reader ) {
T retval;
reader->DoRead(retval);
return retval;
}

class IOReader:
public virtual CanRead<int>,
public virtual CanRead<double>,
public virtual CanRead<long>,
public virtual CanRead<short>,
public virtual CanRead<float>,
public virtual CanRead<char>
{
...
public:
// NON VIRTUAL:
template<typename T>
T GetRead() {
return ::GetRead<T>(this);
}
...
}



Useage:


void test( IOReader* reader ) {
int x = reader->GetRead<int>();
double d;
reader->DoRead(d);
short many[100];
reader->DoReads(many, sizeof(many)/sizeof(many[0]));
};


Lastly, on the implementation side:

class ByteReader {
public:
typedef unsigned char byte;
void ReadBytes( byte* b, int count ); // TODO
template<typename T>
void ReadTyped( T* t, int count=1 ) {
ReadBytes( reinterpret_cast<byte*>(t), count * sizeof(T) );
}
};
template<typename T>
class CanReadImplementation:
public virtual CanRead<T>,
public virtual ByteReader
{
public:
virtual void DoRead( T& value ) {
ReadTyped( &value );
}
virtual void DoReads( T* values, int count) {
ReadTyped( values, count );
}
};




(Assuming, of course, you are reading packed bytes with the correct endianness.)

Share this post


Link to post
Share on other sites
Quote:
Original post by NotAYakk
*** Source Snippet Removed ***

Useage:


void test( IOReader* reader ) {
int x = reader->GetRead<int>();
double d;
reader->DoRead(d);
short many[100];
reader->DoReads(many, sizeof(many)/sizeof(many[0]));
};


Lastly, on the implementation side:
*** Source Snippet Removed ***

(Assuming, of course, you are reading packed bytes with the correct endianness.)

Do NOT do this. Firstly, the virtual inheritance in your example is unecessary (at least in this context it is) and will just bloat the size of your objects and produce longer construction times. As well, since you have separate bases, each with their own virtual functions, each one is going to also have its own vtable pointer, once again bloating the object.

As an example, on my compiler your IOReader type is 48 bytes in size, regardless of the fact that it has no datamembers. If you remove the virtual inheritance, it drops down by half to 24 bytes. If you avoid this method entirely in favor of just putting the virtual functions in the type itself, the size is 4 bytes.

I understand that you are trying to reduce redundancy in code, but unfortunately this is not the best way to do it. If you absolutely must reduce redundancy, preprocessor metaprogramming is the optimal solution here, as it gets rid of the redundant code but keeps the virtual functions declared in one type.

Share this post


Link to post
Share on other sites
Quote:
Original post by Polymorphic OOP
Do NOT do this. Firstly, the virtual inheritance in your example is unecessary (at least in this context it is) and will just bloat the size of your objects and produce longer construction times.


It allows the implementation to be inherited from completely seperately, providing a cleaner seperation between interface and implementation. The implementation of your reader and writer can be replaced with an XML reader/writer, and the client could talk to it without giving a care.

Quote:
As well, since you have separate bases, each with their own virtual functions, each one is going to also have its own vtable pointer, once again bloating the object.


You should have one vtable per class, not one vtable per instance of the class. And the objects in question have empty construction.

Quote:
As an example, on my compiler your IOReader type is 48 bytes in size, regardless of the fact that it has no datamembers. If you remove the virtual inheritance, it drops down by half to 24 bytes. If you avoid this method entirely in favor of just putting the virtual functions in the type itself, the size is 4 bytes.


On many platforms, 48 bytes of space is, well, ignoreable. It's 48 bytes. Unless you are creating one of these for every pixel on the screen, or are writing code for a gameboy...

By 'wasting' 48 bytes, on a system with 1 GB of memory, I've managed to use up 0.0000045% of the system's memory. ;)

Optimization has it's place. That place, in my opinion, is long after you have working, easy to debug, correct code.

Quote:
I understand that you are trying to reduce redundancy in code, but unfortunately this is not the best way to do it. If you absolutely must reduce redundancy, preprocessor metaprogramming is the optimal solution here, as it gets rid of the redundant code but keeps the virtual functions declared in one type.


Preprocessing has it's own issues.

All preprocessor code evaluates to being on one line. So debugging it and walking through it is rather difficult.

Preprocessors foul up the the global name space. Unlike C++ names, they cannot be namespace restricted. So every preprocessor token you define and expose could generate more foulups.

Preprocessors are textual substitutions that pay little to no attention to the syntax of C and C++. This can result in subtle, difficult to catch bugs.

...

Personally, I'd use something like I wrote above. If and when I found that it caused performance problems, I'd change it. But, honestly, I doubt the size of a reader/writer class, nor the construction time of a reader/writer class, would fall into the 10% of the code that eats up 90% of the resources.

The important part is that there is nothing in the design of the above interface that requires the extra overhead. As noted, cludgy macro programming or copy/paste programmeing could be used to create an equivilent interface. I claim that you should wait before writing the macro/copy paste implementation until after you know that the code is involved in a perforamce or resource bottleneck.

Premature optimization is the root of alot of evil.

Share this post


Link to post
Share on other sites
Late reply -- I just got back from vacation.

Quote:
Original post by NotAYakk
Quote:
Original post by Polymorphic OOP
Do NOT do this. Firstly, the virtual inheritance in your example is unecessary (at least in this context it is) and will just bloat the size of your objects and produce longer construction times.


It allows the implementation to be inherited from completely seperately, providing a cleaner seperation between interface and implementation. The implementation of your reader and writer can be replaced with an XML reader/writer, and the client could talk to it without giving a care.

That has nothing at all to do with virtual inheritance. Again, the virtual inheritance here is entirely unecessary.

Quote:
Original post by NotAYakk
Quote:
As well, since you have separate bases, each with their own virtual functions, each one is going to also have its own vtable pointer, once again bloating the object.


You should have one vtable per class, not one vtable per instance of the class.


I said vtable pointer, not vtable. Each one of your base types needs an entirely separate vtable, and as well, each instance needs a vtable pointer. That means that you have a separate vtable pointer for each base meaning that you need N vtable pointers per instance of the child type, where N is the number of virtual function created in this manner. You may as well have just used a separate function pointer for each function.

Quote:
Original post by NotAYakk
And the objects in question have empty construction.

The objects do not have empty construction. Whenever you construct an instance of your type, the vtable pointers and virtual inheritance information need to be initialized.

Quote:
Original post by NotAYakk
Quote:
As an example, on my compiler your IOReader type is 48 bytes in size, regardless of the fact that it has no datamembers. If you remove the virtual inheritance, it drops down by half to 24 bytes. If you avoid this method entirely in favor of just putting the virtual functions in the type itself, the size is 4 bytes.


On many platforms, 48 bytes of space is, well, ignoreable. It's 48 bytes. Unless you are creating one of these for every pixel on the screen, or are writing code for a gameboy...

By 'wasting' 48 bytes, on a system with 1 GB of memory, I've managed to use up 0.0000045% of the system's memory. ;)

Having a lot of memory isn't an excuse to blatantly misuse language constructs to make complex and inefficient solutions to problems which have simple and more efficient alternatives.

Share this post


Link to post
Share on other sites
If you see vtables being a major problem you can always use:

__declspec(novtable)

"It's a Microsoft-specific optimization hint that tells the compiler: this class is never used by itself, but only as a base class for other classes, so don't bother with all that vtable stuff, thank you. The compiler generates no vtable and no vtable initialization code"

Seems like it could be really usefull for Interface Classes, and with defines
you can always disable it on compilers without support and not have to worry
about breaking code.

Linkey:
http://msdn.microsoft.com/msdnmag/issues/0300/c/

Share this post


Link to post
Share on other sites
Quote:
Original post by Polymorphic OOP
Late reply -- I just got back from vacation.


Ah!

Quote:
That has nothing at all to do with virtual inheritance. Again, the virtual inheritance here is entirely unecessary.


Unless you want to avoid the heavy use of macro based and/or copy/paste programming.
Quote:
I said vtable pointer, not vtable. Each one of your base types needs an entirely separate vtable, and as well, each instance needs a vtable pointer. That means that you have a separate vtable pointer for each base meaning that you need N vtable pointers per instance of the child type, where N is the number of virtual function created in this manner. You may as well have just used a separate function pointer for each function.


Good point.

Well, the syntax is much cleaner, and you have compiler guarantees that there is no defined way to change the pointers.

Quote:
The objects do not have empty construction. Whenever you construct an instance of your type, the vtable pointers and virtual inheritance information need to be initialized.


So an int copy, right?

Quote:
Quote:
Original post by NotAYakk
Quote:
As an example, on my compiler your IOReader type is 48 bytes in size, regardless of the fact that it has no datamembers. If you remove the virtual inheritance, it drops down by half to 24 bytes. If you avoid this method entirely in favor of just putting the virtual functions in the type itself, the size is 4 bytes.


On many platforms, 48 bytes of space is, well, ignoreable. It's 48 bytes. Unless you are creating one of these for every pixel on the screen, or are writing code for a gameboy...

By 'wasting' 48 bytes, on a system with 1 GB of memory, I've managed to use up 0.0000045% of the system's memory. ;)

Having a lot of memory isn't an excuse to blatantly misuse language constructs to make complex and inefficient solutions to problems which have simple and more efficient alternatives.


What is the simple solution?

This:

#define DECL_VIRT_READER(type) \\
virtual void DoRead( type& t ) = 0;\\
virtual type GetRead() = 0;\\
virtual void DoReads( type* t, int count = 1 ) = 0

#define DECL_READER_IMPL(type) \\
virtual void DoRead( type& t ) { DoReads( &t, 1 ); };\\
virtual type GetRead() { type tmp; DoRead(tmp); return tmp; };\\
virtual void DoReads( type* t, int count ) { ByteReads( t, sizeof(type)*count ); }






followed by:


class IOReader {
public:
DECL_VIRT_READER(int);
DECL_VIRT_READER(float);
DECL_VIRT_READER(double);
DECL_VIRT_READER(char);
DECL_VIRT_READER(unsigned char);
DECL_VIRT_READER(wchar_t);
};


and:


class IOReaderImpl: ByteReader, IOReader
{
DECL_READER_IMPL(int);
DECL_READER_IMPL(float);
DECL_READER_IMPL(double);
DECL_READER_IMPL(char);
DECL_READER_IMPL(unsigned char);
DECL_READER_IMPL(wchar_t);
}


Or is there an actual cleaner way to do it?

Claiming the existance of a cleaner way to do it, without providing one, isn't that helpful. :)

[Edited by - NotAYakk on August 3, 2006 11:42:28 AM]

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