Working some Template Magic

Pages: 12
A couple of months ago I wrote a class that allowed me to store any single type of variable, it worked kinda' like this:

1
2
3
4
5
6
7
CAbstractVar *pVar = new CVar<int>;
pVar->set<int> ( 5 );
delete pVar;

pVar = new CVar<std::string>;
pVar->set<std::string> ( "Hi!" );
delete pVar;


At least, that's how I remember it. After that, I had to work on a project for game design and forgot about it entirely, then I went and wiped my hard drive for a RAID 0 array without backing up any of my projects (derp). Now I can't for the life of me remember how I pulled off that bit of magic, and I need it before I can get any farther with a project I'm working on. The only thing I can think of is using void pointers, which I am 100% sure isn't the way I went about doing this.

All I know is that the parent class CVarAbstract has template methods for getting and setting the contained value, and that the derived CVar class is a template class and uses that to contain its value. Anyone want to fill in the missing pieces to this puzzle?
Last edited on
Use boost::any; it is exactly what you are describing.

www.boost.org
I've seen that, but I would think that it would be either slow or resource heavy compared to what I had originally, which was only about 30 lines of code. Meh, guess I won't know until I try.
Last edited on
It is neither slow nor resource heavy and is on par with your 30 lines of code.
is boost open source? If it is I want to study their code :O
It is, from the looks of it.

boost::any seems like way more than what I had originally. Mine wasn't as safe probably, but it was a bit easier to use and I'm pretty sure was a bit more light-weight. But the boost::any class won't be accessed constantly, so speed-wise it shouldn't be much of an issue, hopefully.
Last edited on
Boost has its own license, but yes, it is open source.

No way is safe; they all either limit the number of types that can be stored to a predefined set (such as in boost::variant) but are typesafe, or allow any type, but require the user to cast, and are completely un-typesafe.

It is easier to use than what you wrote above, for the simple reason that in your code, I have to:

 
pVar->set<std::string> ( "Hi!" );


but with boost::any I just:

 
pVar = std::string( "Hi!" );


So how do the languages such as python, that are written in c/c++ pull off their var types? They are both typesafe and from what I can tell have no predefined set of types.

Then again I haven't played much with many of the languages like that so i don't know for sure.
But in this case I'll be getting variables more often than you'd be getting them, and it's the accessing of members using C-style programming methods that annoys me. VC++ has Intellisense, and I'd like to be able to use it wherever possible to shave time on the actual coding.

Really, I'd rather not use boost::any. Don't really need a lot of what it offers, and that bulk is making for a relatively slow object compared to what I had originally. I've already made a class that just has the functionality that I need, and it was, on average, 30 times faster than boost::any. I could just use that, but it uses the same C-style method of accessing the data that boost::any has, which is a real pain since the programming style conflicts with the rest of my engine. I just need help with that part really, making Get and Set template methods of the class parent class. This is the only thing I can think of doing:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class CVarAbstract;

template <typename T>
class CVar
	: public CVarAbstract
{
public:
	typedef typename T Type;
	Type Val;
};

class CVarAbstract
{
public:
	template <typename T>
	T Get ( )
	{
		return static_cast<CVar<T>*>(this)->Val;
	}

	template <typename T>
	void Set ( T Val )
	{
		static_cast<CVar<T>*>(this)->Val = Val;
	}
};


But that doesn't work because CVarAbstract isn't defined at the time it's inherited by CVar.
Last edited on
So what does this class do for me? Are you needing it for polymorphism or type erasure?
I'll assume so, otherwise I don't see the point (why not just make a variable of the needed
type then).

I'm going to be having a list of CVarAbstract objects which point to different resources, some may be texture handles, others may be sprite objects, so I needed a way to store them all and still be able to differentiate between their types. Hardly any different from the boost::any class from what I can tell, it just doesn't throw any exceptions.
Don't mind me budging in.. Is this kind of solution one you're looking for:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class CVarAbstract
{
};

template <typename T>
class CVar : public CVarAbstract
{
public:
	T const &Get(void) const { return Val; }
	void Set(T const &NewVal) { Val = NewVal; }
protected:
	T Val;
};

int main(void)
{
	CVar<int> i;
	i.Set(5);
	CVar<std::string> s;
	s.Set("characters");

	std::vector<CVarAbstract *> ArrayOfVars;
	ArrayOfVars.push_back(&i);
	ArrayOfVars.push_back(&s);

	int *j = dynamic_cast<CVar<int> > (ArrayOfVars[0]);
	std::cout << j->Get();  // Prints 5


@NGen: what does your class do if the user attempts to treat the contained data as a type that is not what
it holds or is not convertible from what it holds? Is boost::variant<> an acceptable solution?

@elega: CVarAbstract absolutely must have a virtual destructor. (Not even sure how your code above
works, since CVarAbstract has no vtable, I would think the dynamic_cast wouldn't work).
The class does nothing, the user gets an error which they'll have to debug back to the point where they cast to the wrong type. Speed is an issue, since this is for a video-game after all. I guess the best thing to do, in my opinion, feel free to say it's a bad idea, is to create an abstraction layer that would allow me to swap out boost::any for a simpler, faster, and less safe class that would be used while compiling the release version. If everything's been tested properly then casting to the wrong type shouldn't be an issue. I would be an idiot to say that you can get rid of all possible bugs and errors, but in the case that an error should arise from this I can always compile in Debug mode and attempt to recreate the error using the safer boost::any. This way I get the safety when I need it, but the speed when it's being ran by the end-user.
Last edited on
I would create my own class "any" inside my own namespace "my_boost" and have my any have
the exact same API as boost::any. Then make a typedef for the type that switches between
my any and boost::any depending upon compilation mode.

Abstraction is OK as long as you don't introduce (additional) virtual functions, otherwise there is
a performance hit.

(From an API standpoint, and for debuggability, I would much, much, much rather the object
throw than pretend everything is alright. The problem with your approach is it may lead to
long debug sessions as a result of crashes that occur long after the original problem, which
was that the user made a bad cast.
@jsmith: That is absolutely true. I was a bit lazy with the example :)

@NGen: If speed is your primary concern, then dynamic_cast might be a bad approach. This is avoidable if the dynamic_cast is not needed often and you generally can pass the class by CVar& reference, which is not any slower than any generic class member call.
I think I got it working...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <iostream>
#include <string>
using namespace std;

class BaseType
{
public:

    template <class T>
    T get()
    {
        return *(T*)val_ptr();
    }

    template <class T>
    void set(const T & t)
    {
        *(T*)val_ptr()=t;
    }

    virtual ~BaseType(){}

private:

    virtual void * val_ptr()=0;
};

template <class T>
class Type:public BaseType
{
    T value;

    void * val_ptr()
    {
        return &value;
    }
};

int main()
{
    BaseType * bptr;

    bptr=new Type<int>;
    bptr->set<int>(4);
    cout << bptr->get<int>() << endl;

    delete bptr;

    bptr=new Type<string>;
    bptr->set<string>("asdf");
    cout << bptr->get<string>() << endl;

    delete bptr;

    cin.get();
    return 0;
}

Last edited on
I keep going back to type erasure.

http://www.cplusplus.com/forum/articles/18756/

jsmith wrote:
No way is safe; they all either limit the number of types that can be stored to a predefined set (such as in boost::variant) but are typesafe, or allow any type, but require the user to cast, and are completely un-typesafe.

Check this out! I came up with something that allows any type and is typesafe!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#include <iostream>
#include <vector>
#include <string>
using namespace std;

class BaseType
{
public:

    template <class T>
    operator T();

    template <class T>
    BaseType & operator=(const T & t);

    virtual ~BaseType(){}

    friend ostream & operator<<(ostream & os, const BaseType & bt)
        {return bt.stream_out(os);}

private:

    virtual void * get_type()=0;
    virtual void * val_ptr()=0;
    virtual ostream & stream_out(ostream & os) const =0;
};

template <class T>
class Type:public BaseType
{
    T value;

    //the ADDRESS of this unique-per-T
    //variable will represent the type!
    //isn't that brilliant?! ^_^
    static int type;

    void * val_ptr() {return &value;}
    void * get_type() {return &type;}
    ostream & stream_out(ostream & os) const
        {return os<<value;}

public:

    static void * get_type_static(){return &type;}
    Type(const T & t):value(t){}
    virtual ~Type(){}
};

template <class T>
int Type<T>::type;

template <class T>
BaseType::operator T()
{
    if (Type<T>::get_type_static()==get_type())
        return *(T*)val_ptr();
    else throw("bad cast!");
}

template <class T>
BaseType & BaseType::operator=(const T & t)
{
    if (Type<T>::get_type_static()==get_type())
        {*(T*)val_ptr()=t; return *this;}
    else throw("bad cast!");
}

int main()
{
    vector<BaseType*> container;

    container.push_back(new Type<int>(12));
    container.push_back(new Type<string>("hi!"));
    container.push_back(new Type<double>(0.25));

    for (int i=0; i<container.size(); i++)
        cout << *container[i] << endl;

    int n=*container[0];
    cout << n << endl;

    string str("asdf");
    *container[1]=str;
    cout << *container[1] << endl;

    double d=3.14159;

    try {*container[0]=d;}
    catch(const char * s) {cerr << s <<'\n';}

    try {d=*container[0]=4;}
    catch(const char * s) {cerr << s <<'\n';}

    cout << d << endl << *container[0] << endl;

    cin.get();
    return 0;
}

EDIT: Or... when you say typesafe, you mean on compile-time?...

EDIT 2: No, what I just said was stupid... You can't possibly have a compile-time type-check for dynamically allocated objects...

EDIT 3: Managed to squeeze it into less than 100 lines...
Last edited on
It doesn't work if T implements operator& to do funny things.
(You need to use the boost::addressof() trick to ensure line 38
does what you want it to).


Pages: 12