Sign in to follow this  
darkzerox

templates, structs, typedefs & lists

Recommended Posts

all in one! how might i get this to compile?
template<class T>
struct point { T x, y; };
typedef list< point<?> > line;
first we create struct point, which will take ints, floats, doubles, or what have you, as long as they are the same, right..? then we want "line" to be a list of points, so long as each point has the same type. putting "int" in for the ? will compile, however, that's not what I want. replacing it with "T" also doesnt work. leaving the <?> out altogether doesnt work either.. so ya.. not really sure. also, just for clarification,
struct rect
{
       union { int x1, left; };
       union { int x2, right; };
       union { int y1, top; };
       union { int y2, bottom; };
};
will let me create a rect, and i can refer to left-most vertical line of the rectangle as "x1" or "left" and they are interchangable, correct? ie, if i change 'x1', it changes 'left'?

Share this post


Link to post
Share on other sites
You can't use "T" in your typedef, because it is outside of the template declaration, which ended at the end of your struct. If you don't want to use an actual type in your typedef, I'm not sure what you're trying to do..

Edit: I suppose you could do something like this:

template <class T>
struct point {
typedef std::list<point<T> > line;
T x, y;
};

typedef point<int> IntPoint;
IntPoint::line MyListOfIntPoints;




And yes, that struct of unions will work as you expect it to.

Share this post


Link to post
Share on other sites
Let me guess, you want to define the line type, and then be able to use the line type later on, with varous types for the points' members? Like:

line<int> IntLine;
line<float> FloatLine;
That would be considered a "typdef template", and is not supported in C++. Many people complain about this omission, and it might get added in the next version of C++, but for now, you'll have to live without it. If it was supported, though, it'd probably look like this:

template<class T>
struct point { T x, y; };

template<class T>
typedef list< point<T> > line;
Google for "Typedef Templates" for more information.

Share this post


Link to post
Share on other sites
Try this instead


template<class T>
struct point { T x, y; };

template<class T>
typedef list< point<T> > line;




Hope this helps!

[edit]
Doh! Agony, beat me to it! He is also correct on it not being supported by C++.
[/edit]

Share this post


Link to post
Share on other sites
Quote:
template<class T>
struct point { T x, y; };

template<class T>
typedef list< point<T> > line;


tried that before either of you suggested it, but thanks.
don't see why that shouldn't work... *shakes a fist at the C++ makers*

Share this post


Link to post
Share on other sites
Yeah, it's definitely a highly desired feature. I battled with this problem for a week when I was developing a console parser only to find out it wasn't my code, but the code in general.

Also, from what I've read on google, pragma Fury's example is the most common work around for this.

Happy coding!

Share this post


Link to post
Share on other sites
Quote:
Original post by darkzerox
what is the dif between pragma fury's suggestion, and
typedef list< point<int> > line;
?


None, really.. except that you define both the point type and the list to contain that point type with a single typedef. It also scopes the list to the type of point that it will contain, so it's nicely packaged.

Share this post


Link to post
Share on other sites
Short of making line inherit from list, rather than be a typedef of list, I don't think you can. (And I don't think inheritance is advised either, since STL containers don't have virtual destructors.) I'd probably just create a seperate function that took two points as parameters, and returned a line (i.e., a list of points).

Actually, I'd probably create a seperate line class that internally used a list of points as storage, and provided it's own interface for manipulating the line. This would allow you to create more complex constructors, as well as requiring that the line at all times contains at least two points, as a line with only one or no point is obviously invalid.

Share this post


Link to post
Share on other sites
Quote:
Original post by Agony
Actually, I'd probably create a seperate line class that internally used a list of points as storage, and provided it's own interface for manipulating the line. This would allow you to create more complex constructors, as well as requiring that the line at all times contains at least two points, as a line with only one or no point is obviously invalid.



I thought of that, but the thing is...

If I have some kind of

class line
{
list point<int> p;
}

or whatever, then to add a point to the line, i would have to do line.p.push_back(point(x,y);

whereas i want to do line.push_back(point(x,y));

of course, i suppose i could give line a push_back function which calls p.push_back.... but then i have to do that for every function.. and thats just kinda messy. is there really no better way?

btw, i'm going to make the line constructor use stdarg, so you can input an unlimited amount of points; line(point p, ...);

(a single point line would be valid, it'd just draw a single point. i'm contemplating if i should allow a line with no points... (it just wouldnt do anything).

or... how about something like,

line l = { point<int>(2,3), point<int>(4,5) }

would it possible to get that to work?

[Edited by - darkzerox on June 9, 2005 5:57:40 PM]

Share this post


Link to post
Share on other sites
MWUAHAHAHA.. through some obscure syntax, i may just have got it to work.


template<class T>
class line : public list< point<T> >
{
public:
line( point<T> p )
{
push_back(p);
}
};

line<int> myLine( point<int>(2,3) );








at least it compiles anyway.


[edit]

errr... crap... how do i write this function?


line( point<T> p, ... )
{
push_back(p);

va_list marker;
.
.
.
}






is there really no way to check once you've reached the last arguement???
i mean, without the user having to say "this is the last arguement" in one way or another???

...

[Warning] cannot receive objects of non-POD type `struct point<int>' through `...'


damn it. have i been foiled AGAIN? *another* limitation of C++??? two in one day?! NOOOOOOOOOOO!

someone, rescue me from this infernal mess of limitations! my library won't be complete!

---

okay, i see why you told me to stay away from this. but.. but.. all i want is a line, made up of points. is that really so much to ask?

[Edited by - darkzerox on June 9, 2005 7:40:16 PM]

Share this post


Link to post
Share on other sites
i'm so close! i can smell success. c'mon guys, hang in there. we can finish this thing together!

(yah, i know i'm triple posting...)


template<class T>
struct point {
T x, y;
point(T a, T b) { x = a; y = b; };
};

template<class T>
class line : public list< point<T> >
{
public:
line( point<T> p )
{
push_back(p);
}

void print()
{
for( line<T>::iterator i = begin(); i != end(); i++ )
{
cout << "(" << i->x << "," << i->y << ")";
if( i != --end() ) cout << ",";
}
}
};

int main()
{
line<int> myLine( point<int>(2,3) );
myLine.push_back( point<int>(5,6) );
myLine.print();

getchar();
return 0;
}



it works, it all works...

if someone could just help me with the stdarg/va_list part of it... i'd really appreciate it.

Share this post


Link to post
Share on other sites
Variable-argument functions are generally considered a Bad Idea in C++. They're supported for C backwards-compatibility, and under the hood, they're implemented with some evil hackery. Basically, all the needed data is just dumped on the stack in order, and the function is on its own to figure out how many things there are and what their types are. Normally this is done by putting some magic codes into the first argument, like the format specifiers in a C-style printf(). The function then parses the first argument and grabs the others off the stack one at a time, with the help of a couple of macros (VA_START etc.). Needless to say this is *very* error-prone and spits in the face of all the nice type-checking facilities C++ gives you (just as the dread void* does).

Also, deriving from standard library containers is a possible source of problems - they haven't been designed to be derived from, which could cause problems with destructors. (Which is to say, if you ever store a "line" with a "list<point<T> >", and then try to delete it, it may blow up.) So you should instead consider storing the list as a member. (This will also allow you to control the interface that gets exposed - at the expense of having to do it manually.)

Anyway. If having the calling code just call push_back() repeatedly is really so onerous, you could provide a wrapper with a shorter name - or be really evil, and use an operator() overload, like this:


// Warning! Not tested!
template<class T>
class line {
typedef point<T> point;
list<point> storage;
public:
line(const point& p) : storage(1, p) {}
line(T x, T y) : storage(1, point(x, y)) {}
// Notice how the object returns itself - this allows for "chaining"
line& operator() (const point& p) { storage.push_back(p); return *this; }
// We can also provide for direct creation of points:
line& operator() (T x, T y) { storage.push_back(point(x, y)); return *this; }
};

// I think the '=' form will be required :/
line<int> myLine = line<int>(2,3)(4,5)(6,7);



There are other similar ways to do it; this is my favourite. It's seriously abusive, but it gets the job done and probably won't cause any problems.

Also note that I have opted to pass the points by const reference; this is a good idea whenever you are working with non-primitives (it avoids copying the structure).

Share this post


Link to post
Share on other sites
woahhh.. and i thought *my* code was getting trippy.. haha..

okay. the only reason i didn't want to use the = is because i might want to do some crazy stuff like

if( myLine == line<int>(5,6)(2,3) )
// blah blah


of course, i'd have to overload the == operator too. but without a nifty constructor i'd have to do something like

line myOtherLine;
myOtherLine.push_point( point<int>(5,6) );
myOtherLine.push_point( point<int>(2,3) );
if( myLine == myOtherLine )


which just isnt fun...

i'm going to try yours out and see what happens...

thanks!

oh, and question, what's the purpose of the "1" in "storage(1, p)"??

(ps: it runs... without errors too, unlike mine)




also the function


void print()
{
for( list<point>::iterator i = storage.begin(); i != storage.end(); i++ )
cout << "(" << i->x << "," << i->y << ")" << (i != --storage.end() ? "," : 0);
}




produces some warnings..

63 [Warning] `std::list<point<T>, std::allocator<point<T> > >::iterator' is implicitly a typename
63 [Warning] implicit typename is deprecated, please see the documentation for details


however, i was wondering how I might overload it so that I could do

cout << myLine;

instead...?




ive figured out how to print a point,


template<class T>
ostream& operator<< (ostream& os, const point<T>& p) {
return os << "(" << p.x << "," << p.y << ")";
}


but i still dont have a clue how to print the entire line.




okay.. after thinking real hard.. i've decided that the 1 comes from std::list's constructor.. meaning you are setting the size of storage to 1, and putting 'p' inside it. yay. go me...

i still dont know how to cout << myLine :(

[Edited by - darkzerox on June 9, 2005 9:55:17 PM]

Share this post


Link to post
Share on other sites
Quote:
Original post by Zahlman
Anyway. If having the calling code just call push_back() repeatedly is really so onerous, you could provide a wrapper with a shorter name - or be really evil, and use an operator() overload, like this:

*** Source Snippet Removed ***

There are other similar ways to do it; this is my favourite. It's seriously abusive, but it gets the job done and probably won't cause any problems.

I hadn't thought of such an evil thing myself. Although now I got to wondering if one could overload the comma operator to do something similar, such that (point<T>, point<T>) would create a line<point<T>> object with those two points, (point<T>, line<point<T>>) would create a new line with the point prepended to the given line, and (line<point<T>>, point<T>) would create a new line with the point appended to the given line. And (line<T>, line<T>) would create a new line by combining the given two lines, just for good measure. Though that would require using an extra set of internal parentheses in the constructor call to make the commas act as operators rather than parameter seperators, and there would potentially be a lot of temporary line objects involved. It certainly wouldn't be a high performance piece of code. But

line<point<int> > SomeLine = (point<int>(5, 3), point<int>(7, 2), point<int>(9, 5));
doesn't look too bad, if style is what you're going for.

Share this post


Link to post
Share on other sites
i liked Zahlman's usage of
typedef point<T> point;
as soon as i figured out what it did.
i kept wondering why he didnt need to put <T> in stuff like storage.push_back(point(x, y)).

so yah, you could clean that up a bit by doing

typedef point<int> point;
typedef line<point> line;

and then just do
line someLine = (point(5,3),point(7,2),point(9,5));

you might say this defeats the purpose of having it templated.. but of course, you would typedef this in the function/class where you wanted to use it, thus you could use floats somewhere else :)

but you two already knew this im sure.... (im really just clarifying this for my own sake).

so yah.. thats all awesome.

how do i cout << someLine; ?? haha.. i know, im going to sound like a broken record til i figure it out...

yah.. nice idea of comments... i think i'd rather use the + operator though, would make more sense, wouldnt it?

myLine += point(10,11);

Share this post


Link to post
Share on other sites
okay, after much whining, i got it nearly figured out.


template<class T>
ostream& operator<< (ostream& os, const line<T>& l) {
for( list< point<T> >::const_iterator i = l.storage.begin(); i != l.storage.end(); ++i ) os << *i << (i != --l.storage.end() ? "," : 0);
return os;
}



but how do i get rid of these 2 errors:


In function `std::ostream& operator<<(std::ostream&, const line<T>&)':
69 [Warning] `std::list<point<T>, std::allocator<point<T> > >::const_iterator' is implicitly a typename
69 [Warning] implicit typename is deprecated, please see the documentation for details


?

Share this post


Link to post
Share on other sites
(By the way, I think we're getting out of For Beginners territory and into General Programming now... ;) )

Quote:
Original post by darkzerox
but how do i get rid of these 2 errors:


It's really the same warning. Basically, what it boils down to is that list<point<T> >::const_iterator, without actually going to look up the definition, could be either a typename (a typedef that belongs to the class, like my "point" typedef) or a data member. For the syntax here, a typename would be required, and it actually happens to be a typename once the compiler goes to look it up, so the compiler goes and assumes that's what you meant. The warning means "I'm being smarter than the C++ standard says I have to be, and in the future this code might not compile because of even more complexity being added to the language". Fortunately, the solution is simple:


template<class T>
ostream& operator<< (ostream& os, const line<T>& l) {
for(typename list< point<T> >::const_iterator i = l.storage.begin();
i != l.storage.end(); ++i )
os << *i << (i != --l.storage.end() ? "," : "");
return os;
}


By the way, you probably don't want that 0 in the output line - it's getting interpreted as a (char*) 0, such that the types match, and outputting from a null pointer is a bad idea. What you really want is an empty string, as illustrated.

You can use the + operator as well, just be sure to overload both += and + (and probably, implement + in terms of +=).

As for the '1' in (1, p) - there, (1, p) are parameters for the std::list constructor. (In an initializer list, for non-primitive members you provide constructor arguments rather than a simple value.) The std::list provides a constructor taking an integer n and T (i.e. type of thing in the list) prototype, and initializes itself with n copies of the prototype. Here, we make a list with one copy of p, the first point. :)

Agony: operator, can be overloaded too, but it's tricky to work with and I wouldn't really recommend it. In particular, care must be taken to distinguish ',' intended as a chaining operator from ',' used to separate function parameters - this will likely require redundant parentheses, and become confusing when you try to use it.

Share this post


Link to post
Share on other sites
yah.. i realized what the 1 was after awhile, i just worded it poorly :D

uhm... ah.. i just thought the "" looked tacky, and i could be cool and use a 0 or something instead, but... guess that wasn't such a good idea :D would be cool if you didnt need the "else" portion of that.

typename?? oh boy.. haha.. yet another new term to learn about :D

haha.. thanks. i'll do some more research on typenames :)

Share this post


Link to post
Share on other sites
my fun little library to date.

#include <cstdio>
#include <iostream>
#include <iterator>
#include <list>
using namespace std;

typedef unsigned char byte;

template<class T>
struct rect {
union { T x1, left; };
union { T x2, right; };
union { T y1, top; };
union { T y2, bottom; };
rect(T a, T b, T c, T d) { x1 = a; y1 = b; x2 = c; y2 = d; };
};

template<class T>
struct circle {
T x, y, r;
circle(T a, T b, T c) { x = a; y = b; r = c; };
};

template<class T>
struct ellipse {
T x, y;
union{ T xr, rx; };
union{ T yr, ry; };
ellipse(T a, T b, T c, T d) { x = a; y = b; rx = c; ry = d; };
};

template<class T>
struct point {
T x, y;
point(T a, T b) { x = a; y = b; };
};

template<typename T>
class line {
typedef point<T> point;
public:
list<point> pl;
line(const line& l) : pl(l.pl) {}
line(const point& p) : pl(1, p) {}
line(T x, T y) : pl(1, point(x, y)) {}
// Notice how the object returns itself - this allows for "chaining"
line& operator() (const point& p) { pl.push_back(p); return *this; }
// We can also provide for direct creation of points:
line& operator() (T x, T y) { pl.push_back(point(x, y)); return *this; }
void operator= (const line& l) { pl = l.pl; }
};

template<typename T>
ostream& operator<< (ostream& os, const point<T>& p) {
return os << "(" << p.x << "," << p.y << ")";
}

template<typename T>
ostream& operator<< (ostream& os, const line<T>& l) {
for(typename list< point<T> >::const_iterator i = l.pl.begin(); i != l.pl.end(); ++i )
os << *i << (i != --l.pl.end() ? "," : "");
return os;
}

int main()
{
typedef line<int> line;
line myLine = line(1,2)(3,4)(5,6);
cout << myLine;
getchar();
return 0;
}


i'm going to add SDL drawing compatibility, hopefully with antialiasing.

i'm thinking of having it like

draw(myShape, color, FILLED|ANTIALIASED);

that sound good?

then i'll have an object manager...
some basic screen functions,...

line clipping.. oh boy. it was bad enough with only two points!

anyways, if anyone wants to help me with my library... you're welcome :)

Share this post


Link to post
Share on other sites
Quote:
Original post by Agony
That would be considered a "typdef template", and is not supported in C++. Many people complain about this omission, and it might get added in the next version of C++, but for now, you'll have to live without it. If it was supported, though, it'd probably look like this:


Your lack of C++ knowledge is showing... How do you thing the STL string container works? Observe:


#include <string>

// this string is the same as
std::string thing1;

std::basic_string<char> thing;

// std::string is simply "typedef basic_string<char> string;"



So this way you can use other character types or types that behave like characters to form the string. Is this not what you are referring to? The best way to accomplish this would be to use a template class as you/darkzerox seem to have discovered:

// actual code not tested

template<class T> class Point {
public:
Point(T x_point, T y_point) {
x = x_point;
y = y_point;
}
Point() { }
T x;
T y;
};

// Realize that the T above is simply a place holder.

// let's use our new point

Point<int> point1(0, 0);
Point<float> point2;

// and if you are feeling stupid
Point<char> stupid_point;

/* All of these should work.
*
* Now if you wanna make a list of points, use the STL list or vector container
* and just add some points.
*/


// your special typedef for ints?

typedef Point<int> IntPoint;

IntPoint point3(0, 0,); // this works same as point1 above





@darkzerox
Looking at your design, it could use some work, but you some what have the right idea. I would reccommend reading "The C++ Programming Language" by Bjarne Stroustrup. It would clear all of this right up.

Share this post


Link to post
Share on other sites
Quote:
Original post by TheRealMAN11
Quote:
Original post by Agony
That would be considered a "typdef template", and is not supported in C++. Many people complain about this omission, and it might get added in the next version of C++, but for now, you'll have to live without it. If it was supported, though, it'd probably look like this:


Your lack of C++ knowledge is showing... How do you thing the STL string container works? Observe:


And you don't have a single clue as to what he's talking about do you [headshake]

Quote:
Original post by darkzerox
my fun little library to date.

*** Source Snippet Removed ***


Some general comments.

1. In general always use constructor initializer lists, in your code sometimes you use it then sometimes you do assignment and thats the thing if your not using constructor initializer lists your not doing initialization your doing assignment, constructor initializer lists are stilled called before implicitly.

2. Overloaded assignment operator of "line" is syntactically correct but semantically wrong, assignment operators return a reference to self.

3. For "line" you do not need explicitly define a copy constructor and assignment operator as one is implicitly defined to do a member-wise copy/assignement, std::list has proper assign/copy semantics it does the expected behaviour, prefer it over user-defined ones.

4. All standard library containers are parameterized by allocator type something to think about [wink].

5. ostream is a type alias for std::basic_ostream, make you stream insertion/extraction operators take that instead (where it makes sense).

6. In general prefer using the standard library generic algorithms over explicit handwritten loop code when using containers.


#include <algorithm> // copy
#include <iterator> // ostream_iterator
#include <ostream>

template < typename CharT, typename Traits, typename Tp >
inline std::basic_ostream<CharT, Traits>&
operator<<(std::basic_ostream<CharT, Traits>& out,
const point<Tp>& p) {
return out << CharT('(') << p.x << CharT(',')) << p.y << CharT(')');
}

template <
typename CharT, typename Traits,
typename Tp, typename Alloc
>
inline std::basic_ostream<CharT, Traits>&
operator<<(std::basic_ostream<CharT, Traits>& out,
const line<Tp, Alloc>& l) {
std::copy(l.pl.begin(), l.pl.end(),
std::ostream_iterator<point<Tp>, CharT, Traits>(out));
return out;
}


[Edited by - snk_kid on June 10, 2005 3:25:36 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