Sign in to follow this  

Template Question

This topic is 4598 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

This is my second question on templates. I would like to know if I'm abusing their use, or if what I'm trying to accomplish impossible.
template < typename Object > class IComponent : public Object, boost::noncopyable
{
	typedef boost::signals::connection Connection;

private:

	std::vector< Connection > links;

public:

	void addLink( boost::signal< void() > & signal )
	{
		links.push_back( signal.connect( boost::bind( &Object::run, this ) ) );
	}

	void disconnect()
	{
		std::for_each( links.begin(), links.end(), boost::bind( &Connection::disconnect, _1 ) );
	}

	// virtual void run() = 0;
};


template < typename Object > class IParent : public IComponent< Object >
{
private:

	boost::signal< void() > signal;

public:

	template < typename Child > add( IComponent< Child > & slot )
	{
		slot.addLink( signal );
	}

	void run()
	{
		Object::start();
		signal();
		Object::finish();
	}

	// virtual void start() = 0;
	// virtual void finish() = 0;
};


template < typename Object > class ITransform : public IParent< Object >
{
public:

	void start()
	{
		glPushMatrix();
		Object::transform();
	}

	void finish()
	{
		glPopMatrix();
	}

	// virtual void transform() = 0;
};


In my original implementation I used a pure virtual function table to do the same thing. The vtable method works, but is 10% slower than doing the same thing without vtables (i.e. straight up C style functions in C++). My template code doesn't compile because methods like, Object::start, Object::finish, and Object::transform aren't defined at in the class, only in the instance through inheritance of the template. Like how IParent needs the start and finish methods from the derived ITransform interface. Is there a way to call these non-defined methods in the derived classes using templates? Or is my only option using a vtable? [Edited by - MindCode on May 16, 2005 10:38:17 AM]

Share this post


Link to post
Share on other sites
Nah, this isn't an abuse of templates, although the layout does have me quite confused (I may be able to suggest an alternative if you show some example use cases, the whole "ITransform is-a ... is-a Object" relation has me severly confused).

An alternative to virtuals would be to use even more template mojo. Let's see if I can't get this straight:

template < typename Super , typename Object > class IComponent : public Object, boost::noncopyable
{
typedef boost::signals::connection Connection;
private:
std::vector< Connection > links;
public:
void addLink( boost::signal< void() > & signal ) {
links.push_back( signal.connect( boost::bind( &Super::run, static_cast< Super * >( this ) ) ) );
}
void disconnect() {
std::for_each( links.begin(), links.end(), boost::bind( &Connection::disconnect, _1 ) );
}
// virtual void run() = 0;
};

template < typename Super , typename Object > class IParent : public IComponent< IParent , Object >
{
private:
boost::signal< void() > signal;
public:
template < typename Child > add( IComponent< Child > & slot ) {
slot.addLink( signal );
}
void run() {
static_cast< Super * >( this )->start();
signal();
static_cast< Super * >( this )->finish();
}
// virtual void start() = 0;
// virtual void finish() = 0;
};

template < typename Super , typename Object > class ITransform : public IParent< ITransform , Object >
{
public:
void start()
{
glPushMatrix();

//Instead of manually casting this to super each time, we could
//define a function that does it locally. I.E. instead of:
//static_cast< Super * >( this )->transform();
//we use:
transform();

}
void finish()
{
glPopMatrix();
}
// virtual void transform() = 0;
//And down here, we implement it:
void transform()
{
static_cast< Super * >( this )->transform();
}

};


Note: the use of static_cast should ensure that Super is allways related to *this, and give a decent error if it isn't. It's going to be hard to accidentally screw up enough to give a hard to understand message.

Edit: non cosmetic changes bolded and the actual code underlined.

Edit: fixed accidental "IComponent" where "IParent" was meant.

Share this post


Link to post
Share on other sites
Well here is kind of what I intend to to. Its a small glut based demo for a scene graph using these components. It shows how I would like to to implement and instantiate each component type. Of course it won't compile because of my template problem. Using gcc I get the errors:

'run' is not a member of 'Rotator', instantiated from 'boost::bind( &Object::run, this )' with Object = Rotator

And similar errors for other methods. Somehow it seems that this shouldn't be an error, because the instance of ITransform< Rotator > is also an IParent which does define the 'run' method.

To help resolve some of the confusion I'll explain what some of these methods do. Each IComponent tracks which IParents are linked to it (everytime 'addLink' or 'add' is invoked we create a new link from parent to child). And IComponent can disconnect itself from all IParents connected to it. The important idea is that IComponents (and their derivatives) can be organized into a heirarchical n-tree structure. I derive IParent to contain child IComponents, but I want IParent to be a IComponent so childs can have childs too. I want to implement higher order components, like ITransform, to have more specific behaviour. So an IComponent must have a 'run' method, and IParent defines 'run' but must also have 'start' and 'finish', and ITransform defines 'start' and 'finish' but must also have 'transform'. Later derived classes will define these methods (like in overloaded virtual function, but resolved at compile-time).


#include <vector>

#include <boost/utility.hpp>
#include <boost/bind.hpp>
#include <boost/signals.hpp>
#include <boost/signals/connection.hpp>

#include <gl/glut.h>


template < typename Object > class IComponent : public Object, boost::noncopyable
{
typedef boost::signals::connection Connection;

private:

std::vector< Connection > links;

public:

void addLink( boost::signal< void() > & signal )
{
links.push_back( signal.connect( boost::bind( &Object::run, this ) ) );
}

void disconnect()
{
std::for_each( links.begin(), links.end(), boost::bind( &Connection::disconnect, _1 ) );
}

// virtual void run() = 0;
};


template < typename Object > class IParent : public IComponent< Object >
{
private:

boost::signal< void() > signal;

public:

template < typename Child > void add( IComponent< Child > & slot )
{
slot.addLink( signal );
}

void run()
{
Object::start();
signal();
Object::finish();
}

// virtual void start() = 0;
// virtual void finish() = 0;
};


template < typename Object > class ITransform : public IParent< Object >
{
public:

void start()
{
glPushMatrix();
Object::transform();
}

void finish()
{
glPopMatrix();
}

// virtual void transform() = 0;
};


/*******************************************************************************
*** Some Implementations
******************************************************************************/



struct Shape
{
void run()
{
glutSolidTeapot( 1.0 );
}
};


struct Rotator
{
void transform()
{
glTranslatef( 0.0f, 0.0f, -6.0f );
glRotatef( 90.0f, 0.0f, 1.0f, 0.0f );
}
};


struct Graph
{
void start()
{
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
}
void finish()
{
glutSwapBuffers();
}
}


/*******************************************************************************
*** Glut demo
******************************************************************************/



IComponent< Shape > Render;
IParent< Graph > Scene;
ITransform< Rotator > Spinner;


void Display()
{
Scene.run();
}

int main( int argc, char ** argv )
{
// Glut
glutInit( &argc, argv );
glutInitWindowPosition( 100, 100 );
glutInitWindowSize( 500, 400 );
glutInitDisplayMode( GLUT_DOUBLE | GLUT_DEPTH );
glutCreateWindow( "Scene Graph Demo" );
glutDisplayFunc( Display );
glutIdleFunc( Display );

// Scene graph
Spinner.add( Render );
Scene.add( Spinner );

glutMainLoop();

return 0;
}




Given your suggestion I still don't know how to resolve the instance of each component. What is "Super" in the following?


IComponent< Super, Shape > Render;
IParent< Super, Graph > Scene;
ITransform< Super, Rotator > Spinner;


[Edited by - MindCode on May 16, 2005 10:02:20 AM]

Share this post


Link to post
Share on other sites
Quote:
Original post by chad_420
Damn you guys really ought to use source tags...


I would've, but I wanted to be able to add the text formatting to point out the changes. Unfortunately, this does not work well with the [ source ] tag. As such, I simply reduced the horizontal size for most of the functions (I could've had the opening class brace on the same line as the class keyword, but I didn't think of that until just now ^_^;;)

Share this post


Link to post
Share on other sites
I'll address the easy question first :-):

Quote:
Original post by Mindcode
Given your suggestion I still don't know how to resolve the instance of each component. What is "Super" in the following?

IComponent< Super, Shape >   Render;
IParent< Super, Graph > Scene;
ITransform< Super, Rotator > Spinner;


You can't do these with this system. However, you could not do this with the virtual method either - because if one tried to type:

IComponent< Shape > Render;

One would get an error relating to the fact that "run()" is a pure virtual function, with no definition. That is, one cannot create an IComponent, only a derivation of that class.

Instead, one would do something like:

class RenderComponent : public IComponent< RenderComponent , Object >
{
public:
void run( void )
{
//render the scene
}
};

RenderComponent Render;




For all these cases, the template argument for Super is allways going to be the type that's deriving. E.G:

template < typename Super , typename Object > class IComponent : public Object, boost::noncopyable
template < typename Super , typename Object > class IParent : public IComponent< IParent , Object >
template < typename Super , typename Object > class ITransform : public IParent< ITransform , Object >
class RenderComponent : public IComponent< RenderComponent , Object >

Looking carefully at this snippet:

IComponent< Shape >   Render;
IParent< Graph > Scene;
ITransform< Rotator > Spinner;


I think helps me understand the problem more. Really, I (think) things should look like this:

Shape is-a IComponent
Graph is-a IParent is-a IComponent
Rotator is-a ITransform is-a IParent

You've got the first is-a series reversed, which I think is an attempt at the Super method that I was doing myself :-).

If we go with this setup, just using virtuals:

/*
* Shape is-a IComponent
* IParent is-a IComponent
* ITransform is-a IParent
*/


#include <vector>

#include <boost/utility.hpp>
#include <boost/bind.hpp>
#include <boost/signals.hpp>
#include <boost/signals/connection.hpp>

#include <gl/glut.h>

class IComponent
{
typedef boost::signals::connection Connection;
private:
std::vector< Connection > links;
public:
void addLink( boost::signal< void() > & signal ) {
links.push_back( signal.connect( boost::bind( &IComponent::run, this ) ) );
}
void disconnect() {
std::for_each( links.begin(), links.end(), boost::bind( &Connection::disconnect, _1 ) );
}
virtual void run( void ) = 0;
virtual ~IComponent( void ) {}
};

class IParent : public IComponent
{
private:
boost::signal< void() > signal;
public:
void add( IComponent & slot )
{
slot.addLink( signal );
}

void run()
{
start();
signal();
finish();
}

virtual void start() = 0;
virtual void finish() = 0;
virtual ~IParent( void ) {}
};

class ITransform : public IParent
{
public:
void start()
{
glPushMatrix();
transform();
}

void finish()
{
glPopMatrix();
}

virtual void transform() = 0;
virtual ~ITransform( void ) {}
};

class Shape : public IComponent
{
void run()
{
glColor3f( 1.0 , 1.0 , 1.0 );
glutSolidTeapot( 1.0 );
}
};

struct Translator : public ITransform
{
float x,y,z;
void transform()
{
glTranslatef( x , y , z );
}
};

struct Rotator : public ITransform
{
float r,x,y,z;
void transform()
{
glRotatef( r , x , y , z );
}
};


struct Graph : public IParent
{
void start()
{
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
}
void finish()
{
glutSwapBuffers();
}
};

struct Camera : public IParent
{
void start()
{
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
glOrtho( -2 , 2 , -1.5 , 1.5 , 1.0 , 10.0 );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
}
void finish()
{
}
};

Shape Render;
Graph Scene;
Translator Movement;
Rotator Spinner;
Camera MyCamera;

void Display()
{
MyCamera.run();
Spinner.r += 1.0;
}

void Idle()
{
glutPostRedisplay();
}

int main( int argc, char ** argv )
{
// Glut
glutInit( &argc, argv );
glutInitWindowPosition( 100, 100 );
glutInitWindowSize( 500, 400 );
glutInitDisplayMode( GLUT_DOUBLE | GLUT_DEPTH );
glutCreateWindow( "Scene Graph Demo" );
glutDisplayFunc( Display );
glutIdleFunc( Idle );

// Scene graph
Movement.z = -6.0;

Spinner.r = 90.0;
Spinner.y = 1.0;

Spinner.add( Render );
Movement.add( Spinner );
Scene.add( Movement );
MyCamera.add( Scene );

glutMainLoop();

return 0;
}



Now, the thing you seem to be trying to avoid is when you do:

Rotator MyRotate;
MyRotate.run();

In which case, IParent::run will be called. Unless inlined, this will force the program to make a virtual lookup to ITransform::start and ITransform::stop, which is the problem you're having.

Do I have everything right so far?

EDIT: Some mistakes fixed.
EDIT: Fully compiling and tested as apparently working example made.

[Edited by - MaulingMonkey on May 16, 2005 5:14:08 AM]

Share this post


Link to post
Share on other sites
The problem with your original code is that you expect Object to be the type that provides the entire implementation. This isn't the case though, for example ITransform provides the implementation of the start() and finish() functions, NOT Rotator. By providing another template member as MaulingMonkey has done you can specify where the implementation is.

MaulingMonkey's first reply actually does what you want, with a slight variation. Object is as before, the actual type of the component. Super is the where the expected implementation can be found. In the case where you actually want to use them as you've shown in the example, the templated type Object is both the ultimate base class as well as the implementation. So to use it you need:

IComponent< Shape,Shape > Render;
IParent< Graph,Graph > Scene;
ITransform< Rotator,Rotator > Spinner;

Now, to avoid the redundant specification you can set the default type of the Object template member to be Super. That is

template < typename Super , typename Object=Super >

EDIT: You'll also need to modify IParent::add() since MaulingMonkey forgot to account for the extra template member [smile]

Share this post


Link to post
Share on other sites
Quote:
Original post by joanusdmentia
The problem with your original code is that you expect Object to be the type that provides the entire implementation. This isn't the case though, for example ITransform provides the implementation of the start() and finish() functions, NOT Rotator. By providing another template member as MaulingMonkey has done you can specify where the implementation is.

MaulingMonkey's first reply actually does what you want, with a slight variation. Object is as before, the actual type of the component. Super is the where the expected implementation can be found. In the case where you actually want to use them as you've shown in the example, the templated type Object is both the ultimate base class as well as the implementation. So to use it you need:

IComponent< Shape,Shape > Render;
IParent< Graph,Graph > Scene;
ITransform< Rotator,Rotator > Spinner;

Now, to avoid the redundant specification you can set the default type of the Object template member to be Super. That is

template < typename Super , typename Object=Super >

EDIT: You'll also need to modify IParent::add() since MaulingMonkey forgot to account for the extra template member [smile]


Well I'll be darned, you're right (except you missed some other errors I made ;-)). After many bug fixes (things like: "IComponent< IParent , Object >" really needs to be "IComponent< IParent< Super , Object > , Object >", it "works", by which I mean it compiles and displays the teapot ^_^.

Here's the fully corrected source which somehow works, even though I don't care to try and understand it:

#include <vector>

#include <boost/utility.hpp>
#include <boost/bind.hpp>
#include <boost/signals.hpp>
#include <boost/signals/connection.hpp>

#include <gl/glut.h>

template < typename Super , typename Object = Super > class IComponent : public Object, boost::noncopyable
{
typedef boost::signals::connection Connection;
private:
std::vector< Connection > links;
public:
void addLink( boost::signal< void() > & signal ) {
links.push_back( signal.connect( boost::bind( &Super::run, static_cast< Super * >( this ) ) ) );
}
void disconnect() {
std::for_each( links.begin(), links.end(), boost::bind( &Connection::disconnect, _1 ) );
}
void run( void )
{
static_cast< Super * >( this )->run();
}
};

template < typename Super , typename Object = Super > class IParent : public IComponent< IParent< Super , Object > , Object >
{
private:
boost::signal< void() > signal;
public:
template < typename SuperA , typename Child > void add( IComponent< SuperA , Child > & slot ) {
slot.addLink( signal );
}
void run() {
static_cast< Super * >( this )->start();
signal();
static_cast< Super * >( this )->finish();
}
// virtual void start() = 0;
// virtual void finish() = 0;
};

template < typename Super , typename Object = Super > class ITransform : public IParent< ITransform< Super , Object > , Object >
{
public:
void start()
{
glPushMatrix();

//Instead of manually casting this to super each time, we could
//define a function that does it locally. I.E. instead of:
//static_cast< Super * >( this )->transform();
//we use:
transform();
}
void finish()
{
glPopMatrix();
}
// virtual void transform() = 0;
//And down here, we implement it:
void transform()
{
static_cast< Super * >( this )->transform();
}
};

struct Shape
{
void run()
{
glutSolidTeapot( 1.0 );
}
};


struct Rotator
{
void transform()
{
glTranslatef( 0.0f, 0.0f, -6.0f );
glRotatef( 90.0f, 0.0f, 1.0f, 0.0f );
}
};


struct Graph
{
void start()
{
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
}
void finish()
{
glutSwapBuffers();
}
};

IComponent< Shape > Render;
IParent< Graph > Scene;
ITransform< Rotator > Spinner;

void Display()
{
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
glOrtho( -2.0 , 2.0 , -1.5 , 1.5 , 1.0 , 10.0 );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
Scene.run();
}

void Idle()
{
glutPostRedisplay();
}

int main( int argc, char ** argv )
{
// Glut
glutInit( &argc, argv );
glutInitWindowPosition( 100, 100 );
glutInitWindowSize( 500, 400 );
glutInitDisplayMode( GLUT_DOUBLE | GLUT_DEPTH );
glutCreateWindow( "Scene Graph Demo" );
glutDisplayFunc( Display );
glutIdleFunc( Idle );

// Scene graph
Spinner.add( Render );
Scene.add( Spinner );

glutMainLoop();

return 0;
}

Share this post


Link to post
Share on other sites
Quote:
Original post by MaulingMonkey
Well I'll be darned, you're right (except you missed some other errors I made ;-)). After many bug fixes (things like: "IComponent< IParent , Object >" really needs to be "IComponent< IParent< Super , Object > , Object >", it "works", by which I mean it compiles and displays the teapot ^_^.


Odd, the only things I changed where the IParent::add() and adding the default for the template member, and it compiled fine under VC71. The template parameters for a class will default to the 'current' paremeters when used inside the class (at least they do in VC71, is this standard behaviour?)

ie. the following compiles fine

template<typename Super, typename Object>
class IParent : IComponent<IParent,Object>
{
//...
};

Share this post


Link to post
Share on other sites
Dunno what the standard says - GCC 3.4.2 won't compile the posted fragment if I change this single line like so:

template < typename Super , typename Object = Super > class IParent : public IComponent< IParent< Super , Object > , Object >

make -k all 
'Building file: ../main.cc'
g++ -O0 -g3 -Wall -c -fmessage-length=0 -omain.o ../main.cc
../main.cc:29: error: type/value mismatch at argument 1 in template parameter list for `template<class Super, class Object> class IComponent'
../main.cc:29: error: expected a type, got `IParent'
../main.cc: In function `int main(int, char**)':
../main.cc:133: error: no matching function for call to `IParent<Graph, Graph>::add(ITransform<Rotator, Rotator>&)'
../main.cc: In member function `void IParent<Super, Object>::run() [with Super = Graph, Object = Graph]':
../main.cc:112: instantiated from here
../main.cc:37: error: invalid static_cast from type `IParent<Graph, Graph>* const' to type `Graph*'
../main.cc:112: instantiated from here
../main.cc:39: error: invalid static_cast from type `IParent<Graph, Graph>* const' to type `Graph*'
make: *** [main.o] Error 1
make: Target `all' not remade because of errors.
Build complete for project wtf.1

Share this post


Link to post
Share on other sites
This is funny, I was up late last night working on it and I did the all of the exact same things you guys just mentioned. Accept that I am using GCC 3.4.2 not MSVC, so I ran into the compile error.

I'm thinking now that there isn't going to be any way around this. Maybe I should go back to using virtual functions and take the 10% performance hit.

Share this post


Link to post
Share on other sites
Quote:
Original post by MindCode
This is funny, I was up late last night working on it and I did the all of the exact same things you guys just mentioned. Accept that I am using GCC 3.4.2 not MSVC, so I ran into the compile error.

I'm thinking now that there isn't going to be any way around this. Maybe I should go back to using virtual functions and take the 10% performance hit.


Around what, exactly?

You do realize I posted a fully working version that seems to do exactly what you want with your original class heiarchy, using nothing but templates, right? The last errors mentioned only creep up if you don't fully specify the template arguments allways, which is easily fixable by fully specifying them - as the last code I posted in a [ source ] tag does (which compiled, linked, and ran with no visible bugs or problems).

I can probably create a cleaner, completely templatized version of all this using the alternative class heiarchy I posted (with the Shape is-a IComponent stuff) that eliminates all the virtuals and is still idiomatic. Edit: matter of fact, it ends up nearly the same. Here it is:

#include <vector>

#include <boost/utility.hpp>
#include <boost/bind.hpp>
#include <boost/signals.hpp>
#include <boost/signals/connection.hpp>

#include <gl/glut.h>

template < typename Super >
class IComponent
{
typedef boost::signals::connection Connection;
private:
std::vector< Connection > links;
public:
void addLink( boost::signal< void() > & signal ) {
links.push_back( signal.connect( boost::bind( &IComponent::run, this ) ) );
}
void disconnect() {
std::for_each( links.begin(), links.end(), boost::bind( &Connection::disconnect, _1 ) );
}
//virtual void run( void ) = 0;
inline void run ( void ) {
static_cast< Super * >( this )->run();
}

//virtual ~IComponent( void ) {}
};

template < typename Super >
class IParent : public IComponent< IParent< Super > >
{
private:
boost::signal< void() > signal;
public:
template < typename ParentT >
void add( IComponent< ParentT > & slot )
{
slot.addLink( signal );
}

void run()
{
start();
signal();
finish();
}

//virtual void start() = 0;
inline void start()
{
static_cast< Super * >( this )->start();
}
//virtual void finish() = 0;
inline void finish()
{
static_cast< Super * >( this )->finish();
}
//virtual ~IParent( void ) {}
};

template < typename Super >
class ITransform : public IParent< ITransform< Super > >
{
public:
void start()
{
glPushMatrix();
transform();
}

void finish()
{
glPopMatrix();
}

//virtual void transform() = 0;
inline void transform()
{
static_cast< Super * >( this )->transform();
}
//virtual ~ITransform( void ) {}
};

struct Shape : public IComponent< Shape >
{
void run()
{
glColor3f( 1.0 , 1.0 , 1.0 );
glutSolidTeapot( 1.0 );
}
};

struct Translator : public ITransform< Translator >
{
float x,y,z;
void transform()
{
glTranslatef( x , y , z );
}
};

struct Rotator : public ITransform< Rotator >
{
float r,x,y,z;
void transform()
{
glRotatef( r , x , y , z );
}
};


struct Graph : public IParent< Graph >
{
void start()
{
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
}
void finish()
{
glutSwapBuffers();
}
};

struct Camera : public IParent< Camera >
{
void start()
{
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
glOrtho( -2 , 2 , -1.5 , 1.5 , 1.0 , 10.0 );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
}
void finish()
{
}
};

Shape Render;
Graph Scene;
Translator Movement;
Rotator Spinner;
Camera MyCamera;

void Display()
{
MyCamera.run();
Spinner.r += 1.0;
}

void Idle()
{
glutPostRedisplay();
}

int main( int argc, char ** argv )
{
// Glut
glutInit( &argc, argv );
glutInitWindowPosition( 100, 100 );
glutInitWindowSize( 500, 400 );
glutInitDisplayMode( GLUT_DOUBLE | GLUT_DEPTH );
glutCreateWindow( "Scene Graph Demo" );
glutDisplayFunc( Display );
glutIdleFunc( Idle );

// Scene graph
Movement.z = -6.0;

Spinner.r = 90.0;
Spinner.y = 1.0;

Spinner.add( Render );
Movement.add( Spinner );
Scene.add( Movement );
MyCamera.add( Scene );

glutMainLoop();

return 0;
}



Note: main changes were from this pattern:

abstract (that is, not instantiatable) classes:

class ITransform : public IParent
to:
template < typename Super >
class ITransform : public IParent< ITransform< Super > >

implemented-by-superclass function declerations:

virtual void start() = 0;
to:
inline void start()
{
static_cast< Super * >( this )->start();
}

concrete (that is, instantiatable) classes:

struct Shape : public IComponent
to:
struct Shape : public IComponent< Shape >

And finally, the fix for the single compilation error I made in switching this code to templates: a mistake in scope from the original, "class Shape" should have been "struct Shape" or similar to make Shape::run public rather than private, in keeping with Translator, Rotator, and Graph.

[Edited by - MaulingMonkey on May 16, 2005 11:19:42 AM]

Share this post


Link to post
Share on other sites
I'm sorry, I did miss that code you posted. I must have been out of it when I woke up this morning. and/or confused because at first glance it looked like something I tried last night.

I copied and pasted your work. It compiles and links, but it also failed at runtime and raised a program exception window. I don't know how it managed to run on your machine but not on mine. What compiler are you using?

I then tried some subtle modification because I thought I knew where the error was coming from, but it still didn't work. I know this must be getting annoying for you by now MaulingMonkey, and maybe I'm too newb when it comes to templates (I've tended to avoid them in the past). I did have a question about one of your methods.


inline void run ( void ) {
static_cast< Super * >( this )->run();
}



By doing this am I not just reimplementing my own hackish version of a vtable? From what I understand about the pointer cast this cannot be resolved at compile time for template specialization, because 'this' is not known to be a valid subset of 'Super' at compile-time (or else Super::run would be valid). It would have to resolve the object pointed to, and the function refered by that object at runtime. Of course I'm not sure; I don't know that much about how the compiler optimizes. That's why I thought I would point it out and ask.

However, if I find out that my guess is right, then I will go back to using vtables, simply because they are easier to implement and more natural to what I am trying to do. And like I said before, eat the +/-10% performance hit (which, if I'm right, wouldn't be avoided by doing it this way). In fact I may just go back to vtables anyway, because the code is so much easier to read. Throwing template names around just doesn't seem as intuitive a solution.

I have also thought about using boost::function, which will have a similar hit as virtual functions, only that they provide greater flexibility.

[Edited by - MindCode on May 16, 2005 3:11:50 PM]

Share this post


Link to post
Share on other sites
Quote:
Original post by MindCode
I'm sorry, I did miss that code you posted. I must have been out of it when I woke up this morning. and/or confused because at first glance it looked like something I tried last night.

I copied and pasted your work. It compiles and links, but it also failed at runtime and raised a program exception window. I don't know how it managed to run on your machine but not on mine. What compiler are you using?


MinGW's GCC 3.4.2, with Boost 1.32. I'm not seeing any glaring defects in my code... is there any detail in the window about what exactly is failing? If not, does running it through a debugger produce the same results, or does it provide any other information? Can you tell where it fails? Step through the program?

Quote:
I then tried some subtle modification because I thought I knew where the error was coming from, but it still didn't work. I know this must be getting annoying for you by now MaulingMonkey, and maybe I'm too newb when it comes to templates (I've tended to avoid them in the past). I did have a question about one of your methods.


Nah, for the most part I don't use this type of pattern, so it's a good exercise for my brain :-).

Quote:

inline void run ( void ) {
static_cast< Super * >( this )->run();
}



By doing this am I not just reimplementing my own hackish version of a vtable?


Kind of, but no. Assuming the compiler is smart enough, which I'm guessing it probably is, calls to this run function will be completely replaced with calls to Super's run function. The difference is that figuring out which version of run() is to be called not only can be, but must be figured out at compile time. If we were reimplementing the vtable it'd look moe like:

inline void run( void ) {
this->run_vtable_entry();
}

The key here being that "run_vtable_entry()" is a function pointer, which could point to anything at runtime, because multiple derived types can share the same base.

Now, with the template method, we're actually compiling the run function repeatedly, on a per-derivation instance. That is, instead of having:

IComponent::run()

Which has to be able to call any version of run(), we have:

IComponent< Super , Object >::run()

Which allways calls a single version of run().

Quote:
From what I understand about the pointer cast this cannot be resolved at compile time for template specialization, because 'this' is not known to be a valid subset of 'Super' at compile-time (or else Super::run would be valid).


Kind of. What's really happening is that, no, we can't be certain, as it's entirely possible to write:

class Foo : public IComponent< /*Super = */ Bar , ... >;

Now, in the same sense, static_cast< Super * >( this ) is just as invalid, and to some extent it somewhat is. However, this is a procedural call, and we can have the full class outline (that is, class definitions with variables and function prototypes) to confirm our statement against.

So, I'm pretty sure it's valid by the standard, but not 100% so. I've seen similar patterns, especially in the Boost Spirit library, although quickly skimming through the header code for the grammar class looks like they may use a different method for implementation than static_cast.

Quote:
It would have to resolve the object pointed to, and the function refered by that object at runtime. Of course I'm not sure; I don't know that much about how the compiler optimizes. That's why I thought I would point it out and ask.


The thing is that we're static_cast<>ing to a type, and then calling a non virtual function. if ( typeof( this ) != typeof( Super )), the program will err in that Super::run() will be called even though this is not a type of Super.

This differs from the virtual lookup - it's the difference between saying "this is a Super, call it's run() function" and "this has a run() function, look it up since we don't know what this is".

Quote:
However, if I find out that my guess is right, then I will go back to using vtables, simply because they are easier to implement and more natural to what I am trying to do. And like I said before, eat the +/-10% performance hit (which, if I'm right, wouldn't be avoided by doing it this way). In fact I may just go back to vtables anyway, because the code is so much easier to read. Throwing template names around just doesn't seem as intuitive a solution.

I have also thought about using boost::function, which will have a similar hit as virtual functions, only that they provide greater flexibility.


If you're pointing to the point where the series of run() functions are called, then yes, that part is a lot like a virtual lookup replacement, although there are some key differences.

One of the main hits with virtuals is the fact that you have to look up the function to use. The signals method stores the functions to call all in one place, rather than checking this->vtable->run_function each time. That is, the preformance increase is similar to taking this code:

for ( int i = 0 ; i < parents ; ++i )
{
parent[ i ]->vtable->run( /* this = */ &(parent[ i ]) );
}

and changing it to:

for ( int i = 0 ; i < parents ; ++i )
{
function[ i ]( /* this = */ &(parent[ i ]) );
}

The templates version I was showing focuses on making run() faster by eliminating the un-necessary virtual lookups within that piece of code, rather than making the calling of run() faster, which you've allready done*.

* It could still be improved ever so slightly, probably, but not by much. The fact is that at that point you ARE replicating vtables with your connections list, with the key difference being deciding when you actually use the vtable.

Share this post


Link to post
Share on other sites

This topic is 4598 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