templates, structs, typedefs & lists

Started by
26 comments, last by darkzerox 18 years, 10 months ago
ahh... okay.


now lets say I have

typedef list< point<int> > line;

and I want to add a constructor to it, so that I could do

line myLine(point(2,3),point(5,6));

how would I do this?
Advertisement
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.
"We should have a great fewer disputes in the world if words were taken for what they are, the signs of our ideas only, and not for things themselves." - John Locke
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]
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]
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.
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).
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]
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.
"We should have a great fewer disputes in the world if words were taken for what they are, the signs of our ideas only, and not for things themselves." - John Locke
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);
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

?

This topic is closed to new replies.

Advertisement