Jump to content
  • Advertisement
Sign in to follow this  
dmatter

Does anyone know a way around 'virtual templates'?

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

Let me explain with (much simplified but hopefully still applicable) code:
using namespace std;

//these daft struct represent a type of archive class implementing static(compile-time) polymorphism
template <class T>
struct somebase
{
    T i;
    somebase() : i(5) { }
};

struct someobject : somebase<int>
{
};


//The following is what I want to achive but obviously its not valid
struct image_loader
{
    template <class T> //virtual template
    virtual void load(T& ar, image& img) = 0;
};

struct bmp_loader : public image_loader
{
    template <class T>
    void load(T& ar, image& img);
};

//elsewhere in the land of oZ
map<const char*, image_loader*> img_loaders;


Now I apologize for using struct everywhere (its been a long day). Ok, let me clarify, the somebase and someobject represent a special type of file archive that can be serialized to/from, they use static polymorphism and hence require that they passed as a template parameter to the image_loader's 'load' member. Basically what I want is to be able to have a stl map (as demonstrated) of image loaders but be able to pass both an image and the archive (as a template param) so the image can be loaded by the correct loader (in this case a bitmap loader). I can't think of an elagant way around the problem (I have tried honest but I keep getting back to 'if only virtual templates were allowed' [wink]) Now like i said its been a long day so I might well be overlooking something silly, easy and obvious as to the solution. Anyway I'm sooo tired so any questions can be answered tomoz [smile] Any help is welcome Thanks Dave

Share this post


Link to post
Share on other sites
Advertisement
Instead of doing what you're doing, why not try template specialization?


struct image_loader
{
template <class T> //virtual template
void load(T& ar, image& img); // this doesn't have to be virtual!
};




Just define "image_loader::load" in different places, based on the derived type...


template <typename T, typename DerivedType>
void load(DerivedType &d, T &ar, image &img);

// now specialize!!

template<>
void image_loader::load<int, my_image_loader>(my_image_loader &mil, int &ar, image &img)
{
// in here, use the "mil" reference
}


Share this post


Link to post
Share on other sites
BTW you don't have to give a definition for your templated function... until you try to use it! In other words, it should be in the same translation unit as the place you use it...

IOW... define the template specialization in the same .cpp file as the place where you use it.

You can do many template specializations... as many as you need.

Share this post


Link to post
Share on other sites
You have a few options. Let's take a look at some extremely simplified code:

#include <iostream>
struct implementation1 {
int foo () { return 1; }
};

struct implementation2 {
int foo () { return 2; }
};

struct interface {
template < typename implementation_type >
virtual void bar( implementation_type & i ) = 0;
};

struct interface_implementor : interface {
template < typename implementation_type >
virtual void bar( implementation_type & i ) {
using namespace std;
cout << i.foo() << endl;
}
};

int main () {
interface_implementator ii;
implementation1 i1;
implementation2 i2;
ii.bar( i1 );
ii.bar( i2 );
}

There are a few ways to refactor this code into something that compiles.

First, we could make all the implementation# classes derive from an interface base. In this case, such a class would look something like:
struct implementation_base {
virtual int foo () = 0;
};

And then of course our implementation# classes would need to drive from this class:
struct implementation1 : implementation_base {
int foo () { return 1; }
};

All the interface related functions are updated into non templatized versions:
    template < typename implementation_type >
virtual void bar( implementation_type_base & i ) = 0;


Second, we could approach the problem from the other end - and constrain the interface types instead. Example:

struct interface {
virtual void do_something_with( int ) = 0;

template < typename implementation_type >
void bar( implementation_type & i ) {
do_something_with( i.foo() );
}
};

struct interface_implementor : interface {
void do_something_with( int i ) {
using namespace std;
cout << i << endl;
}
};


There are more options, but they start getting complex. When what both classes need to be very flexible, you enter the realm of needing multiple dispatch. Multiple dispatch can be a pain in languages which only support single-dispatch directly, especially if they're statically typed. Go figure that C++ would be a staticly typed language with no direct support for multiple dispatch.

There are workarounds, but a full coverage of multiple dispatch... we'll call it "out of the scope of this post" and direct you to google for more information on those situations :-).

Let's take your (actual) example now, your image loaders. We can apply either of the two mentioned refactorings to these classes.

Assuming we want to keep the main classes looking the same, we can assume we're going to need to pass our archive through a virtual function at some point. In order to deal with this, we need to make all our archives look "the same" - by which I mean, manipulatable through a single base. Assuming we can't control all the archive types passed to *_loader, we need to make a wrapper. Here's an example one - it consists of two parts, an interface, and an templatized implementation of that interface. For this example, I'll assume the only operations we preform on "ar" are reading bytes. Here's the end product:
struct archive_interface {
virtual char get_me_a_byte() = 0;
};

template < typename T >
class archive : public archive_interface {
T & ar;
public:
archive( T & ar ) : ar( ar ) {}
virtual char get_me_a_byte() {
char byte;
ar >> byte;
return byte;
}
};

For *_loader, we put in their first "real" function - "load_impl", only with slightly different parameters - and let's make it protected. This is the "ugly" version which does everything, there's no real reason to make it available to the public.

Here's what this version can look like:
class image_loader {
public:
/* Dosn't compile yet:
* template < class T >
* virtual void load( T & ar , image & img ) = 0;
*/
protected:
virtual void load_impl( archive_interface & ar , image & img ) = 0;
};

class dummy_example_image_loader {
public:
/* Dosn't compile yet:
* template < class T >
* virtual void load( T & ar , image & img ) {}
*/
protected:
virtual void load_impl( archive_interface & ar , image & img ) {
int width = (ar.get_me_a_byte() << 8) + (ar.get_me_a_byte() << 0);
int height = (ar.get_me_a_byte() << 8) + (ar.get_me_a_byte() << 0);
for ( .. ; a_long_time ; .. ) {
/* add data from ar to img */
}
}
};

Okay, so now what? We still don't have anything for the general public!

This part's easy. We've implemented all the per-class stuff within load_impl - for both image_loader and all it's children, the function "load" will look the same - it will simply create an archive wrapper to pass to load_impl.

So does it need to be virtual? No! It will call virtual functions to take care of that. Remove the commented version of load found within dummy_example_image_loader, uncomment the one in image_loader, and implement it:
template < class T >
virtual void load( T & ar , image & img ) {
archive_wrapper ar_w( ar );
load_impl( ar_w , img );

}


[Edited by - MaulingMonkey on August 27, 2005 11:42:10 PM]

Share this post


Link to post
Share on other sites
I'm not sure exactly what you are asking, because I haven't programmed much lately. However I get the idea that it might be the problem that I had once, and somehow solved.

I had a file that stored data, some of it was even nested using braces. I needed a way to read it into objects. Not only that but I wanted to be able to have the data format flexible because I didn't want to add lots of stuff when default parameters could be used.

Ok I wanted to be able to load up a struct S whenever one was found in the file. Ok one is found, now I need to read in its attributes but not all are present. To make it simpler everything was in this format: % name value
I'm not sure if the % was really needed or if it was just to help with the nesting, anyway.

I had a base struct Attribute with no template stuff, just a string for the attribute name and a virtual Load function. Then I had a templated derived class. It had a default value of type T, a current value of T (actually a stack, as it supported nesting) and a conversion operator to type T. It implemented Load by using the input stream operator of T into the current value.

so I would have TemplatedAttribute<A> AttributeA(defaultA);//and similar for others. The constructor added it to a std::map or something like that.

Then in the construtor for S I would so this:
a=AttributeA;
b=AttributeB;
c=AttributeC;

Somewhere inbtween there is this statemachine that handles calling Load on the right Attribute, by matching its stored string against the name of the attribute found in the file.

So in summary: two classes, one derived from the other. The base class has a virtual function, the the derived implements it in terms of T. So that std::map never interacts with the T because it operates through pointers to base, but you never have to do any casting because where you use the data you have access to the actual TemplatedAttribute object. Hope that this was relevant.

Share this post


Link to post
Share on other sites
Great solutions people!!!

I did actually get a working solution last night but scrapped it - something involving static function variable, or static constants, or something, anyway it wasn't very nifty (damn ugly too), the use of templates meant the compiler was creating too many specializations, creating unnessesary bloat - or something like that.[smile].

@Verg - If I understand you correctly that means creating new specialization for each type which would prevent the user adding new types (which is half the point of doing things this way).

@MaulingMonkey - woah! What a lot to read[wink], but well worth it. The class structures are pretty fixed (there is some flexiblility) but they are based on older code that is pretty integrated under-the-hood.
I am very impressed with your wrapper idea (since it allows the class structures to remain the same) and have adapted the idea without any trouble to my actual code - 'et voila, one v-happy man!'
I made a complete wrapper implementing all the c++ primtive data types and moved the wrapper into my code base as I think it complements my archive/stream/serialization library very well.

@Glak - Interesting to read athough I was unable to draw anything I could apply to my code (you seem to have had a more involved problem).

I did some searching on [google], search term 'c++ virtual-templates' and found some interesting info on the problem. I did read that possible the reason c++ doesn't support them is because of the lack of ideas about how to implement it (obviously it would be o-so-difficult to add efficiently).

Appreciate all the help
Dave

Share this post


Link to post
Share on other sites
Looks more like a problem for runtime polymorphism than compile time polymorphism. Specifically, if the archive type was itself polymorphic, you could take it by base pointer instead of template parameter, and the whole thing would be terribly simple.

Share this post


Link to post
Share on other sites
True, that would be easier, although it has two downsides:
First its less efficient, with compile-time polymorphism the compiler may optimize and inline such as down to a simple stream function such as 'ofstream << t'.
Second ideally the archive class would need 'virtual templates' (the very problem I had above) in order to serialize class-types too and static polymorphism allowed me to implement that easily.
edit: although I'm aware that using a runtime polymorphic wrapper impeades on performance (minimal though it is), the archive classes weren't actually intended for this sort of use.

The downside ofcourse to using polymorphism at compile-time is that there is no non-template base class to pass to functions.

Share this post


Link to post
Share on other sites
Quote:
Original post by dmatter
@MaulingMonkey - woah! What a lot to read[wink], but well worth it. The class structures are pretty fixed (there is some flexiblility) but they are based on older code that is pretty integrated under-the-hood.
I am very impressed with your wrapper idea (since it allows the class structures to remain the same) and have adapted the idea without any trouble to my actual code - 'et voila, one v-happy man!'
I made a complete wrapper implementing all the c++ primtive data types and moved the wrapper into my code base as I think it complements my archive/stream/serialization library very well.


Glad to hear it's been of help :-).

Quote:
I did some searching on [google], search term 'c++ virtual-templates' and found some interesting info on the problem. I did read that possible the reason c++ doesn't support them is because of the lack of ideas about how to implement it (obviously it would be o-so-difficult to add efficiently).


I'm aware of at least one way it could be implemented that wouldn't be too hard I believe (specializations may make things a pain), although it would necessitate linking together the linker and compiler stages more than most compiler implementors are probably ready to accept, unless standardized.

Also, the possibility of introducing templatized virtuals is at least being evaluated by the standards comittee, although AFAIK no version has been decided upon as of yet.

I've got a lot of information shelved on multiple dispatch (and potential implementations within C++), I'm hoping to implement some wrappers to implement the concept within a library. It's complex enough that a single method is not going to be enough, I'm convinced. In order to make my code nice and extensible, I've been delving into techniques for variable numbers of template arguments - and revamping my development environment at the same time has delayed all this.

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!