[comp.lang.c++] solution to implementing parameterized classes

jamesh@cs.umr.edu (James Hartley) (06/08/91)

I have seen many requests for generic classes, so I am posting my 
solution for a parameterized queue class developed with Borland C++ 2.0 
via preprocessor tricks.  Stacks can likewise be implemented with minimal 
changes.  Interested readers should refer to _Programming in C++_ by Dew-
hurst & Stark (Prentice-Hall, 1989;  ISBN:  0-13-723156-3) pp. 88-91 and 
_C++ Primer_ by Lippman (Addison-Wesley, 1989;  ISBN:  0-201-16487-6) pp. 
145-148.  If you have any further questions, feel free to contact me by 
email.  I will summarize to the newsgroup if sufficient interests exists.

/************************ begin code ***********************************/

// FILE:  main.cpp  *****

#include <iostream.h>
#include <generic.h>
#include "unqueue.h"	// hides needed instantiations

main() {
    queue(unsigned) q;	// type changed to unsignedqueue by preprocessor
    q << 1;
    q << 2;
    q << 3;
    while (!q.empty()) {
	unsigned v;
	q >> v;
	cout << v << "  ";
    }
    cout << "\n";
    return 0;
}

// FILE:  unqueue.h  *****

#ifndef __UNQUEUE_H
#   define __UNQUEUE_H

#   include <generic.h>
#   include "queue.h"

    declare(node,unsigned);	// preprocessor signal to expand macros
    declare(list,unsigned);
    declare(queue,unsigned);

#endif

// FILE:  queue.h  *****

#ifndef __QUEUE_H
#   define __QUEUE_H

#   include <generic.h>
#   include "list.h"

#   define queue(TYPE)	_Paste2(TYPE,queue)	// _Paste2 == name2 of AT&T
#   define queuedeclare(TYPE) \
    \
    class queue(TYPE) : public list(TYPE) { \
    	void pure()  { }	/* allows instantiations of class */ \
    public: \
	boolean operator << (TYPE &v)  { return append(v); } \
	boolean operator >> (TYPE &v)  { return pop(v); } \
    };

#endif

// FILE:  list.h  *****

#ifndef __LIST_H
#   define __LIST_H

#   include <generic.h>
#   include "boolean.h"

#   define node(TYPE)	_Paste2(TYPE,node)
#   define list(TYPE)	_Paste2(TYPE,list)
#   define listdeclare(TYPE) \
    \
    class list(TYPE);		/* forward reference */ \
    \
    class node(TYPE) { \
	friend class list(TYPE); \
	node(TYPE) *next; \
	TYPE value; \
	node(TYPE)(TYPE &v)  { value = v;  next = 0; } \
    }; \
    \
    class list(TYPE) { \
	node(TYPE) *head, *tail; \
    protected: \
	list(TYPE)(TYPE &v)  { head = tail = new node(TYPE)(v); } \
	list(TYPE)()  { head = tail = 0; } \
	virtual void pure() = 0;	/* forces abstract class status */ \
	boolean append(TYPE&); \
	boolean push(TYPE&); \
	boolean pop(TYPE&); \
    public: \
	boolean empty()  { return boolean(head == 0); } \
    };

#endif

// FILE:  boolean.h  *****

#ifndef __BOOLEAN_H
#   define __BOOLEAN_H

    enum boolean { false, true };

#endif

// FILE:  unqueue.cpp  *****

#include <generic.h>
#include "unqueue.h"
#include "list.cpp"

implement(list,unsigned);	// preprocessor signal to expand macro

// FILE:  list.cpp  *****

#include "list.h"

#define node(TYPE)	_Paste2(TYPE,node)
#define list(TYPE)	_Paste2(TYPE,list)
#define listimplement(TYPE) \
\
boolean \
list(TYPE)::append(TYPE &v) { \
    node(TYPE) *p = new node(TYPE)(v); \
    if (!p) 			/* check for unsuccessful allocation */ \
	return false; \
    if (!head) \
	head = tail = p; 	/* first addition of a node to the list */ \
    else \
	tail = tail->next = p; 	/* subsequent node additions to list */ \
    return true; \
} \
\
boolean \
list(TYPE)::push(TYPE &v) { \
    node(TYPE) *p = new node(TYPE)(v); \
    if (!p)			/* check for unsuccessful allocation */ \
	return false; \
    if (!head) \
	head = tail = p;	/* first addition of a node to the list */ \
    else {			/* subsequent node additions to list */ \
	p->next = head; \
	head = p; \
    } \
    return true; \
} \
\
boolean \
list(TYPE)::pop(TYPE &v) { \
    if (!head)			/* check for empty list */ \
	return false; \
    v = head->value; \
    node(TYPE) *p = head->next; /* retain pointer to 2nd node in list */ \
    delete head; 		/* deallocate spent node */ \
    head = p;			/* reset head pointer */ \
    return true; \
}

/************************** end code ***********************************/
-- 
James J. Hartley               _   /| Internet: jamesh@cs.umr.edu
Department of Computer Science \'o.O'   Bitnet: jamesh@cs.umr.edu@umrvmb.bitnet
University of Missouri - Rolla =(___)=    UUCP: ...!uunet!cs.umr.edu!jamesh
"Life is like an analogy..."      U    ACK!  PHFFT!

miron@cs.sfu.ca (Miron Cuperman) (06/09/91)

Why work hard?  There is a cpp with template support at:
	csc.ti.com:/pub/cpp.tar.Z

btw, I just noticed they have a new version.

--	
	By Miron Cuperman <miron@cs.sfu.ca>

sean@ms.uky.edu (Sean Casey) (06/10/91)

miron@cs.sfu.ca (Miron Cuperman) writes:

|Why work hard?  There is a cpp with template support at:
|	csc.ti.com:/pub/cpp.tar.Z

"With template support" means you can do templates, but it's not
standard. Plus there's no directions therein for using the
preprocessor with one's favorite compiler.

Plus if I read this right, it's an extra step to go through, since
the trend is to put the preprocessor in the compiler.

I wanted to use cpp to do "standard" templates, but their method
didn't meet the Stroustrup model as much as I'd like, so I decided not
to use it.

Sean

-*-*-*-*-

On an unrelated note, C++ is giving me headaches. I'm doing my best to
write clean, sensible looking code, and it still looks awful when I
come back to it.

I've been writing C code for 9 years. I can do nice clean work when I
want. But a little voice is telling me maybe C++ is a bit too low
level for doing lots of OO things. The little voice is telling me to
look at Eiffel or some other such entity to write reasonably
understood systems.

Does that make sense? I wish there were some serious OO design experts
around here to consult with. It'd be kind of nice to hear: "Well
you're having problems because of the way you designed it." or "You
have to tough it out - this kind of problem is just as gnarly in this
language as it is in C++." or "Are you crazy? You should be using
Smalltalk!"

I dunno. Maybe I'll just take more aspirin and type on...

Sean
-- 
** Sean Casey  <sean@s.ms.uky.edu>

nagle@well.sf.ca.us (John Nagle) (06/11/91)

     The notion that the solution to providing generics in C++ involves a
template preprocessor feeding a C++ preprocessor feeding a C preprocessor
feeding a C compiler leads me to suspect that something is very wrong.

     As does the notion that "(void *)" should be a normal part of programming.

					John Nagle

     

jbuck@forney.berkeley.edu (Joe Buck) (06/11/91)

In article <25349@well.sf.ca.us> nagle@well.sf.ca.us (John Nagle) writes:
>
>     The notion that the solution to providing generics in C++ involves a
>template preprocessor feeding a C++ preprocessor feeding a C preprocessor
>feeding a C compiler leads me to suspect that something is very wrong.

I suspect that the people at TI who wrote the thing would agree with you.
Think of it as a temporary solution that suffices until compiler vendors
support templates.  This is equivalent to what the Unix programmer does
when he/she prototypes a program by writing a shell script, then later
codes it in C or C++ for better efficiency.

--
--
Joe Buck
jbuck@galileo.berkeley.edu	 {uunet,ucbvax}!galileo.berkeley.edu!jbuck	

al@well.sf.ca.us (Alfred Fontes) (06/11/91)

miron@cs.sfu.ca (Miron Cuperman) writes:

>Why work hard?  There is a cpp with template support at:
>	csc.ti.com:/pub/cpp.tar.Z

Is there a way to get this without ftp (bbs or mail server, or could
someone possibly mail it to me)?

Al Fontes, Jr.
al@well.sf.ca.us

dag@control.lth.se (Dag Bruck) (06/11/91)

In article <25349@well.sf.ca.us> nagle@well.sf.ca.us (John Nagle) writes:
>
>     The notion that the solution to providing generics in C++ involves a
>template preprocessor feeding a C++ preprocessor feeding a C preprocessor
>feeding a C compiler leads me to suspect that something is very wrong.

To me it suggests that people have successfully managed to build on
existing tools.  Maybe I would understand your point if you elaborated.


		-- Dag Bruck

mattel@auto-trol.com (Matt Telles) (06/12/91)

In article <25349@well.sf.ca.us> nagle@well.sf.ca.us (John Nagle) writes:
>
>     As does the notion that "(void *)" should be a normal part of programming.
>
>					John Nagle
>
>     
   FINALLY!  Someone else that dislikes the idea of a pointer to nothing ...

Can't we define a type called generic????  (You know, like a generic pointer).

Matt


-- 
==============================================================================
Matt Telles 	                 mattel@auto-trol.COM
                                 {...}ncar!ico!auto-trol!mattel
Auto-trol Technology 12500 N Washington Denver, CO 80241-2404 (303)252-2874

chased@rbbb.Eng.Sun.COM (David Chase) (06/12/91)

mattel@auto-trol.com (Matt Telles) writes:

>nagle@well.sf.ca.us (John Nagle) writes:
>> As does the notion that "(void *)" should be a normal part of programming.

>  FINALLY!  Someone else that dislikes the idea of a pointer to nothing ...
>Can't we define a type called generic????  (You know, like a generic pointer).

I'm afraid you missed the point.  If the only way to write reusable
code turns out to be to use "generic" (or "void *") everywhere, what's
the point of pretending to have a type checker?  People griped about
this for Cedar Mesa and Modula-2+, but at least there they had
run-time checking of type conversions from generic-to-specific.

David Chase
Sun

steve@taumet.com (Stephen Clamage) (06/12/91)

mattel@auto-trol.com (Matt Telles) writes:

>In article <25349@well.sf.ca.us> nagle@well.sf.ca.us (John Nagle) writes:
>>     As does the notion that "(void *)" should be a normal part of programming.
>>					John Nagle
>   FINALLY!  Someone else that dislikes the idea of a pointer to nothing ...

>Can't we define a type called generic????  (You know, like a generic pointer).

Absolutely!!  In fact, we can do this without any changes to the language:

	typedef void * generic_ptr;

Now we can write things like

	generic_ptr memchr(const generic_ptr ptr, int val, size_t len);

which makes it crystal-clear that we are working with generic pointers,
not with "pointers to nothing".  This is certainly much, much better than
having to learn the complicated rule that "void*" means "generic pointer".
-- 

Steve Clamage, TauMetric Corp, steve@taumet.com

gkt@iitmax.iit.edu (George Thiruvathukal) (06/13/91)

In article <1991Jun11.183133.11458@auto-trol.com>, mattel@auto-trol.com (Matt Telles) writes:
>    FINALLY!  Someone else that dislikes the idea of a pointer to nothing ...
> Can't we define a type called generic????  (You know, like a generic pointer).
> Matt

...or perhaps one could be allowed to declare variables of type void, given its
distinguished status as a "scalar" type :-).  Then one can declare variables 
which hold nothing and for which no space is allocated.

-- 
George Thiruvathukal

Laboratory for Parallel Computing and Languages
Illinois Institute of Technology
Chicago

mattel@auto-trol.com (Matt Telles) (06/13/91)

In article <1991Jun12.171947.28079@iitmax.iit.edu> gkt@iitmax.iit.edu (George Thiruvathukal) writes:
>In article <1991Jun11.183133.11458@auto-trol.com>, mattel@auto-trol.com (Matt Telles) writes:
>>    FINALLY!  Someone else that dislikes the idea of a pointer to nothing ...
>> Can't we define a type called generic????  (You know, like a generic pointer).
>> Matt
>
>...or perhaps one could be allowed to declare variables of type void, given its
>distinguished status as a "scalar" type :-).  Then one can declare variables 
>which hold nothing and for which no space is allocated.
>
>-- 
>George Thiruvathukal
>
>Laboratory for Parallel Computing and Languages
>Illinois Institute of Technology
>Chicago

Hey, I LIKE it!!  We could call it Write-Only-Memory (or WOM for short)...

This could start a whole new programming trend .. non-functional languages...

:) (For the humor impaired...)

Matt.

-- 
==============================================================================
Matt Telles 	                 mattel@auto-trol.COM
                                 {...}ncar!ico!auto-trol!mattel
Auto-trol Technology 12500 N Washington Denver, CO 80241-2404 (303)252-2874

ttoupin@zephyr.cair.du.edu (Aerin) (06/13/91)

In article <1991Jun12.171947.28079@iitmax.iit.edu> gkt@iitmax.iit.edu (George Thiruvathukal) writes:
>In article <1991Jun11.183133.11458@auto-trol.com>, mattel@auto-trol.com (Matt Telles) writes:
>>    FINALLY!  Someone else that dislikes the idea of a pointer to nothing ...
>> Can't we define a type called generic????  (You know, like a generic pointer).
>> Matt
>
>...or perhaps one could be allowed to declare variables of type void, given its
>distinguished status as a "scalar" type :-).  Then one can declare variables 
>which hold nothing and for which no space is allocated.

I like the idea of a generic type (leads to: generic pointers), something like

	int xxx=3;
	type thing=xxx, *ptr=&xxx;

Then `thing' acts as an <int> type with value `3'.  The `type' structure would
hold a mangled name for the type it stores; maybe "i" for thing and "Pi" for
ptr.  A secondary mangled name would be allowed for casting, so that the
object doe not loose its integrity after a cast (it still knows that it is a
... when it has been cast to a ...).
 
It would be nice, then, to be able to do something like

	if(thing==<int>)
		// something if thing holds an <int> type
	else
		if(thing==<float *(*)[]>)
			// something if thing holds ...  THAT

In addition, functions should come equipped with two variables, like `this':

	arguments - an argument list (type arglist) of what the function was
		called with

	retval - the return value

So, one can do

	int myprintf(const char *p ...) {
		if(arguments==<const char *p,float,int *,char []>)
			// special case...  Does something neat :)
		else
			printf(p ...);
	}

Or, better yet:

	myprintf.arguments=<"%s",7.3,&xxx,"blah">;
		// do something here
	myprintf.call(); // calls myprintf with preloaded arguments...

With preloaded arguments, one might specify arguments to a destructor, among
other things.  Well, that's my $0.02.  Comments?
>-- 
>George Thiruvathukal
>
>Laboratory for Parallel Computing and Languages
>Illinois Institute of Technology
>Chicago

--
Tory S. Toupin                         |
ttoupin@diana.cair.du.edu              |  Existence toward perfection...
Unversity of Denver                    |      Life of mediocrity! 
Undergraduate: Math & Computer Sciences| 
Denver, CO  80208                      |         - M. E.

-----
Ceci n'est pas une signature.

niklas@appli.se (Niklas Hallqvist) (06/15/91)

chased@rbbb.Eng.Sun.COM (David Chase) writes:

>mattel@auto-trol.com (Matt Telles) writes:

>>nagle@well.sf.ca.us (John Nagle) writes:
>>> As does the notion that "(void *)" should be a normal part of programming.

>>  FINALLY!  Someone else that dislikes the idea of a pointer to nothing ...
>>Can't we define a type called generic????  (You know, like a generic pointer).

>I'm afraid you missed the point.  If the only way to write reusable
>code turns out to be to use "generic" (or "void *") everywhere, what's
>the point of pretending to have a type checker?  People griped about
>this for Cedar Mesa and Modula-2+, but at least there they had
>run-time checking of type conversions from generic-to-specific.

Maybe I'm missing the point here too.  You still can have a strongly
typed language, as long as you carefully encapsulate all type-unsafe
casts into small classes where a reader easily can see that you don't
lie.  Example follows:

template<class T> class Stack_of_p : Stack<void*> {
public:
  void push(T* t) { Stack<void*>::push(t); }
  T* pop() { return (T*)Stack<void*>::pop(); }
  //...
};

It's easy to see that every Stack_of_p<T>::push(T*) implies
a cast from T* to void*, and thus the reverse cast in
Stack_of_p<T>::pop() is type-safe!  (In case the usual
stack semantics apply, of course...)

				Niklas

-- 
Niklas Hallqvist	Phone: +46-(0)31-40 75 00
Applitron Datasystem	Fax:   +46-(0)31-83 39 50
Molndalsvagen 95	Email: niklas@appli.se
S-412 63  GOTEBORG, Sweden     mcsun!sunic!chalmers!appli!niklas