Jump to content
  • Advertisement
Sign in to follow this  
MindCode

Template Question

This topic is 4906 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
Advertisement
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
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!