[comp.lang.c++] Comparison functions for qsort

chip@tct.uucp (Chip Salzenberg) (12/25/90)

[This discussion is relevant to both the C and C++ languages; hence
 the cross-post.  Please direct followups appropriately.]

According to rfg@NCD.COM (Ron Guilmette):
>Both qsort() and bsearch() have a similar need to be fed a pointer to a
>function which itself takes two parameters of type pointer-to-T, where
>the type T is the same for both of the two parameters (but is never
>actually the type `void').

Bzzt.  Thanks for playing, though.

Both qsort() and bsearch() require a pointer to a function which does
*actually* take two parameters of type pointer-to-void.  The fact that
the function in question will almost immediately cast those pointers
into pointer-to-T is utterly irrelevant.

Remember that qsort() cannot know the real type of the objects it is
sorting.  Remember also (as noted in an unquoted portion of Ron's
article) that on word-addressed machines, pointer-to-T and
pointer-to-void may differ in representation and/or size.  It is
therefore obvious that qsort() is unable to make a correct call to a
function that has pointer parameters of any type other than
pointer-to-void (or, equivalently, pointer-to-char).

If you declare a comparison function that take parameters of type
pointer-to-T, and pass the address of that function to qsort(), and
qsort() still works, you're quite fortunate (or unfortunate, depending
on how you look at it).  But that doesn't change the fact that such a
practice is not, nor can it ever be, portable.

>But what if that compare routine is stashed away in a precompiled library?

Then the person who wrote it doesn't know what he's doing, and you
should stop using his code immediately.

>In short, IT IS IMPOSSIBLE TO WRITE QSORT() ENTIRELY IN ANSI C OR IN C++
>IN SUCH A WAY THAT IT IS COMPLETELY PORTABLE.

This statement is false.  It is entirely possible to do so, thanks to
ANSI's guarantee that pointer-to-char and pointer-to-void will always
have identical representations.
-- 
Chip Salzenberg at Teltronics/TCT     <chip@tct.uucp>, <uunet!pdn!tct!chip>
"Please don't send me any more of yer scandalous email, Mr. Salzenberg..."
		-- Bruce Becker

barmar@think.com (Barry Margolin) (12/26/90)

In article <277640DD.4A70@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes:
>>In short, IT IS IMPOSSIBLE TO WRITE QSORT() ENTIRELY IN ANSI C OR IN C++
>>IN SUCH A WAY THAT IT IS COMPLETELY PORTABLE.
>This statement is false.  It is entirely possible to do so, thanks to
>ANSI's guarantee that pointer-to-char and pointer-to-void will always
>have identical representations.

I think Chip is correct about C, but not necessarily about C++.  Actually,
qsort() itself can be written portably in C++, but it's hard to write most
comparison functions portably.  The problem is that C++ has restrictions on
casting from void* into <type>*.  So, you can pass the void* parameters to
the comparison function, but it may not be able to do anything with them.

--
Barry Margolin, Thinking Machines Corp.

barmar@think.com
{uunet,harvard}!think!barmar

chip@tct.uucp (Chip Salzenberg) (12/26/90)

According to barmar@think.com (Barry Margolin):
>The problem is that C++ has restrictions on casting from void* into <type>*.

I can find no such restriction in E&S.  In section 4.6 (pages 35-38) I
find this relevant passage:

"[Commentary] ANSI C allows an IMPLICIT conversion from a pointer to
|void| to a pointer to another object type (but not to a pointer to
function type); in C++ a |void*| cannot be assigned to an object of
any type other than |void*| WITHOUT AN EXPLICIT CAST." [Emphasis mine.]

So a comparison function may legally and safely cast a |void*| back to
its original type, though it will take an explicit cast to do so.

Casting from |A*| to |void*| to |B*|, of course, is Right Out in both
C and C++.
-- 
Chip Salzenberg at Teltronics/TCT     <chip@tct.uucp>, <uunet!pdn!tct!chip>
"Please don't send me any more of yer scandalous email, Mr. Salzenberg..."
		-- Bruce Becker

warsaw@nlm.nih.gov (Barry A. Warsaw) (12/26/90)

	barmar> The problem is that C++ has restrictions on casting from
	barmar> void* into <type>*.

Specifically, what are these restrictions?

	barmar> -- Barry Margolin, Thinking Machines Corp.

-Barry
NAME:  Barry A. Warsaw         INET: warsaw@nlm.nih.gov
TELE:  (301) 496-1936          UUCP: uunet!nlm.nih.gov!warsaw

rmartin@clear.com (Bob Martin) (12/27/90)

In article <277640DD.4A70@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes:
>[This discussion is relevant to both the C and C++ languages; hence
> the cross-post.  Please direct followups appropriately.]

	This followup involves only C++ and the newsgroup has been adjusted
	accordingly.
>
>Both qsort() and bsearch() require a pointer to a function which does
>*actually* take two parameters of type pointer-to-void.  The fact that
>the function in question will almost immediately cast those pointers
>into pointer-to-T is utterly irrelevant.

	I dissagree.  As Ron said in his article.  It is always possible
	to cast a pointer to anything to a pointer to void.  But it is
	not guaranteed that you can cast a pointer to void back to 
	its original.  Case in point is a class derived from multiple bases:

		class AB : public A, public B;

	Now take the following variable:
		AB *ab;

	The following two casts work just fine:  
		(A *)ab;  (B *)ab;

	But the following, though it may compile, is likely not to work properly
	because it miscalculates the address of at least one of the base classes.
		(A *)(void *)ab; or (B *)(void *)ab;

	Yet if qsort is to sort objects of type AB, then this is precicely what
	the void* implementation of compar would be asked to do!  Worse yet
	the compiler can't catch this error and will thus allow corrupted pointers
	loose in your program.  Amost certainly the program will crash.

>
>Remember that qsort() cannot know the real type of the objects it is
>sorting.  

	Again I agree with Ron.  qsort/bsearch should be templated functions.  
	Better yet they should be member functions embedded in some kind of
	container class.

-- 
+-Robert C. Martin-----+:RRR:::CCC:M:::::M:| Nobody is responsible for |
| rmartin@clear.com    |:R::R:C::::M:M:M:M:| my words but me.  I want  |
| uunet!clrcom!rmartin |:RRR::C::::M::M::M:| all the credit, and all   |
+----------------------+:R::R::CCC:M:::::M:| the blame.  So there.     |

barmar@think.com (Barry Margolin) (12/27/90)

In article <WARSAW.90Dec26104259@warsaw.nlm.nih.gov> warsaw@nlm.nih.gov writes:
>	barmar> The problem is that C++ has restrictions on casting from
>	barmar> void* into <type>*.
>Specifically, what are these restrictions?

A week or so ago someone mentioned in this list that there is a problem
such conversions and multiple inheritance.  However, I can't find any
mention of it in the "C++ Primer".

I think the problem he described was when a class D is derived from B1 and
B2, and you have the following declarations:

	B1* b1p;
	B2* b2p;
	D* dp;
	void* vp;

	vp = (void*) dp;// OK, explicit cast is optional
	dp = (D*) vp;	// OK, requires explicit cast
	b1p = (B1*) vp; // ??
	b2p = (B2*) vp; // ??

One of the last two conversions is questionable because it requires the
pointer to be offset to the beginning of the appropriate base portion of
the object.  However, the offset depends on the actual type of the object
that vp is pointing to.  Casting back to the type from which the void* was
cast is easy, but casting to a base class effectively requires a virtual
function call, and it's not clear that this is done automatically.

This comes up because the comparison function may be designed to compare
objects of a base class, so it will presumably first cast its void*
argument to a <base>* and then do whatever it does.

Actually, there *is* a solution.  The routine that casts to void* (the
caller of the qsort-like function) can first cast to the appropriate
<base>*, e.g.

	vp = (void*) ((B1*) dp);
	b1p = (B1*) vp;

This works because C++ always knows how to convert a derived pointer to any
of its base class pointers.  However, it's really inconvenient for the
kinds of functions we're discussing (qsort() and bsearch()) because it
doesn't allow arrays/trees to be operated on in place; the caller must
build a second array/tree containing void* pointers whose values come from
the above double cast, pass this to the function, and then cast the results
back to the original types.

--
Barry Margolin, Thinking Machines Corp.

barmar@think.com
{uunet,harvard}!think!barmar

barmar@think.com (Barry Margolin) (01/03/91)

In article <1990Dec26.160636.15566@clear.com> rmartin@clear.com (Bob Martin) writes:
>	I dissagree.  As Ron said in his article.  It is always possible
>	to cast a pointer to anything to a pointer to void.  But it is
>	not guaranteed that you can cast a pointer to void back to 
>	its original.  

This was a point I made in my article a week or two ago, and I was
corrected.  You can always cast a pointer to void back to its original
type.  However, if you cast T* to void*, you can't always cast the void* to
<parent-of-T>*.

>		       Case in point is a class derived from multiple bases:
>
>		class AB : public A, public B;
>
>	Now take the following variable:
>		AB *ab;
>
>	The following two casts work just fine:  
>		(A *)ab;  (B *)ab;
>
>	But the following, though it may compile, is likely not to work properly
>	because it miscalculates the address of at least one of the base classes.
>		(A *)(void *)ab; or (B *)(void *)ab;

But the following do work:

	(A *) (AB *) (void *) ab;
	(B *) (AB *) (void *) ab;

>	Yet if qsort is to sort objects of type AB, then this is precicely what
>	the void* implementation of compar would be asked to do!  

No, that is only true if you are using a compar function intended for
sorting As or Bs.  If it is expecting ABs then it will work fine.
--
Barry Margolin, Thinking Machines Corp.

barmar@think.com
{uunet,harvard}!think!barmar

mat@mole-end.UUCP (Mark A Terribile) (01/03/91)

> >Both qsort() and bsearch() require a pointer to a function which [takes]
> >two parameters of type pointer-to-void.  ... that the function ... will
> >almost immediately cast [them to] pointer-to-T is utterly irrelevant.
 
> 	...  It is always possible to cast a pointer to anything to a pointer
>	to void.  But it is not guaranteed that you can cast a pointer to
>	void back to its original.  ...

Eh?  So long as it's not a pointer-to-function, it sure is guaranteed possible,
or I'm a monkey's uncle.  (Which I ain't.  So there.)
 
> >Remember that qsort() cannot know the real type of the objects it is
> >sorting.  
 
> 	Again I agree with Ron.  qsort/bsearch should be templated functions.  
> 	Better yet they should be member functions embedded in some kind of
> 	container class.

I think that we've been railroaded up a type system branch line and out onto
a siding with no end-of-track warning and no bumber.

Fust of all, the ``array'' 'pon which qsort() and it's ilk work must be a
true homogeneous array.  In other words, everything's gotta be the same size
so the exchanges will work and everything's gotta have enough the same
structure that the comparison functions will work.  This may mean that qsort()
or its buddies is to be given an array of pointers.

Second, the real conflict isn't between  void*  and  XYZ*  and it has nothing
to do with MI.  NOTHING, NOTHING, NOTHING, NOTHING.  NO THING.  NOT A THING IN
THIS WORLD.  NOTHING.  The MI problem occurs when you have pointers that are
stored as  derived*  and interpreted as base* .  That problem *COULD* occur
here, but it's not an essential part of the problem.  In other words, it's a
herring that has gone beyond red and is well on its way to scarlet.

The problem is that  qsort()  wants to use a generic pointer  ( void* )  to
address its `stuff' and so it wants to call a compare function that looks
like

		int compare( void*, void* )

while the function will necessarily be of type

		int compare( const Stuff*, const Stuff* )

(or  const Stuff**, const Stuff** , or whatever).

Thus, qsort takes (or SHOULD take) as a parameter an argument of the form

		int (*cmpr_fun)( void*, void* )

and we want to pass it an argument of the form

		int (*cmpr_fun)( const Stuff*, const Stuff* )

This means that we must coerce (by cast) a pointer-to-function of one type
to the type of the other pointer-to-function.

I want to believe that this is harmless; I am inclined to believe that on
most machines it is, so long as we supply the compare function and it knows
what of what  Stuff  we are asking  qsort()  to speak.  (What if it's not
harmless?  More a little later ...)

What is probably DISASTROUS is to use the ellipsis in declaring  qsort() .
If  qsort() 's argument is of the form   int (*cmpr_fun)( ... )  the function
may get called (and under some MS-DOS compilers, *WILL* get called) with an
entirely different calling sequence, such that the function will misunderstand
its arguments and even its call/return sequence.

What compilers and why?  The Glockenspiel port of  cfront , at least, and
because ordinary member functions of fixed signature can be called using the
Pascal calling sequence, which is smaller and faster than the C calling
sequence (because the size of the argument list to be released in the Pascal
calling sequence is known definitely to the callED function, which can
specify in the return statement that the stack is to be so adjusted--and this
is not possible in the C calling sequence, in which the callING function must
strip the arguments).  (Unfortunately, if the Pascal calling sequence is used,
the function is marked as Pascal in the object file and the frotzenglarken
linker insists on ignoring case when comparing function names.)

In other words, Sun Microsystems has indeed made a horrible mistake.  You've
just got to trust the user to pun the types correctly here, at least until
we get a better solution.  The ellipsis is dead wrong.

	Once again, for those who came late:

		The ellipsis in the prototype of the compare function
		pointer in the prototype for  qsort()  (or any other generic
		sorting or searching function) is ABSOLUTELY, POSITIVELY,
		IRRETREVIEVABLY, D-E-A-D DEAD WRONG.

But isn't this a matter for the ANSI committee, as well as for Sun?  I wish
them well, I sure do ...

Templates can provide a better solution, provided that it's not necessary to
either (a) compile in a whole new version of the  qsort()  code for each type
to be handled, or (b) introduce additional out-of-line function calls into
each internal step of the sort.

But templates don't answer the problem completely.  What happens if you
have something like a database engine that has to handle whatever types and
sizes are thrown at it without recompilation?  Then you need the flexibility
provided by the current  qsort() .

There's another problem, and a it's real killer.  What if  void*  is different
in size and even in how (e.g., in what register or stack) it must be passed
from  Stuff* ?  Then you CAN'T pass an
int (*cmpr_fun)( const Stuff*, const Stuff* )  to  qsort()  (which,
remember, wants an  int (*cmpr_fun)( void*, void* ) .  This means that if
your compare function is  int strcmp( const char*, const char* ) , you really
DO have to wrap it in a function like

	int str_as_void_cmp( const void* s1, const void* s2 )
	{
		return strcmp( (const char*) s1, (const char*) s2 );
	}

Furthermore, it won't do any good to declare it inline, because a REAL pointer
and REAL operations are needed.

Again, templates can help.

It would also be nice (and may become important in the world of C++ and
templates) if global optimizers realized that all  F()  does is call  G()
with exactly the same (to the machine) parameters, and replace the call to
F()  outright  with the call to  G() .  This requires optimization AFTER
symbol resolution but before relocation--in other words, optimization
BETWEEN THE LINKING PHASES.  Such may be a real need in C++ with templates.
Of course, if the function  F()  is declared inline, the code generator has
information sufficient to perform the optimization, IF its optimizer can make
use of the information.  Again, it may become important to do so soon.
(Instead of removing the call to  F() , it may be possible to turn  F() into
a jump into a point inside G() and perhaps just after G()'s prologue--if the
appropriate label is available.)

Such optimization would allow us to write the `correct' code (correct in that
we explicitly type-pun and take responsibility for the consequences) while
allowing the code to run without extra cost on machines on which that is
indeed possible.
-- 

 (This man's opinions are his own.)
 From mole-end				Mark Terribile

Bruce.Hoult@bbs.actrix.gen.nz (01/05/91)

Barry Margolin writes:
>Bob Martin writes:
>>		       Case in point is a class derived from multiple bases:
>>
>>		class AB : public A, public B;
>>
>>	Now take the following variable:
>>		AB *ab;
>>
>>	The following two casts work just fine:  
>>		(A *)ab;  (B *)ab;
>>
>>	But the following, though it may compile, is likely not to work properly
>>	because it miscalculates the address of at least one of the base classes.
>>		(A *)(void *)ab; or (B *)(void *)ab;
> 
>But the following do work:
>
>	(A *) (AB *) (void *) ab;
>	(B *) (AB *) (void *) ab;


That doesn't solve the problem in question, since the compare function will
most likely know that it is comparing A's or B's, but not that they are
embedded in AB's.  After all, if you're writing an A or B compare function
that *does* know this, then you might as well write a special one for AB's
anyway.

What *will* work is...

    (A*) (void*)(A*) ab;    or
    (B*) (void*)(B*) ab;
-- 
Bruce.Hoult@bbs.actrix.gen.nz   Twisted pair: +64 4 772 116
BIX: brucehoult                 Last Resort:  PO Box 4145 Wellington, NZ

hansen@pegasus.att.com (Tony L. Hansen) (01/06/91)

Lest everyone gets lost in the discussions on what's good and bad with
qsort()'s (and bsearch()'s) definition, I'm going to iterate here the proper
way to use qsort() which avoids all problems on all machine types and works
everywhere:

    o	First off, the proper prototypes are:

	void *bsearch(const void*key, const void *base,
	    size_t nmemb, size_t size,
	    int (*compar)(const void*, const void*));

	void qsort(void *base, size_t nmemb, size_t size,
	    int (*compar)(const void*, const void*));

	These functions are required to be declared within <stdlib.h>.
	This is from Section 4.10.5.1 and 4.10.5.2 of the ANSI C standard,
	which is also a base document for the draft ANSI C++.

    o	For a given type T of which an array is being searched or sorted,
	the comparison function must be declared:

	int Tcomparison(const void *a, const void *b)
	{
	    const T *Ta = (const T*)a;
	    const T *Tb = (const T*)b;
	    // now do whatever you want to compare *Ta and *Tb
	}

    o	Note that the type cast MUST be to type T and not to any base class
	of T. (This is particularly a requirement when T is multiply
	inherited.) After casting to T*, you may THEN cast to a base class of
	T if you wish, but you can't go directly to that base class from the
	void*.

Once templates become available, it will be possible to create a template
version of qsort() which looks something like this:

	template <class T>
	void qsort(T *base, size_t nmemb, int (*compar)(const T*, const T*));

(It should even be possible to implement the template qsort() using the void*
version.)

					Tony Hansen
				att!pegasus!hansen, attmail!tony
				    hansen@pegasus.att.com
				      tony@attmail.com

hansen@pegasus.att.com (Tony L. Hansen) (01/06/91)

<< From: chip@tct.uucp (Chip Salzenberg)
<< Both qsort() and bsearch() require a pointer to a function which does
<< *actually* take two parameters of type pointer-to-void.  The fact that
<< the function in question will almost immediately cast those pointers
<< into pointer-to-T is utterly irrelevant.

< From: rmartin@clear.com (Bob Martin)
< I dissagree.  As Ron said in his article.  It is always possible to
< cast a pointer to anything to a pointer to void.  But it is not
< guaranteed that you can cast a pointer to void back to its original.
< Case in point is a class derived from multiple bases:
<	class AB : public A, public B;
< Now take the following variable:
<	AB *ab;
< The following two casts work just fine:  
<	(A *)ab;  (B *)ab;
< But the following, though it may compile, is likely not to work
< properly because it miscalculates the address of at least one of the
< base classes.
<	(A *)(void *)ab; or (B *)(void *)ab;
< Yet if qsort is to sort objects of type AB, then this is precicely what
< the void* implementation of compar would be asked to do!  Worse yet the
< compiler can't catch this error and will thus allow corrupted pointers
< loose in your program.  Amost certainly the program will crash.

Bob, you're seriously mixing apples and oranges, and getting caught in the
middle. You're right, you cannot take a void* created from an AB* and cast it
directly to a B* and expect things to work out right. SO DON'T DO IT! No
comparison function passed to qsort() should ever do that! You're passing an
AB* cast to a void*, so cast the void* back to an AB*!  If you choose to go
on from there to an A* or a B*, fine. But don't go directly from the void* to
an A* or B*. In other words, declare your comparison function like this:

	int comparison(const void *a, const void *b)
	{
	    const AB *ABa = (const AB*)a;
	    const AB *ABb = (const AB*)b;
	    // compare *ABa with *ABb
	}

If you do so, everything works out just fine.

See another article of mine on a brief description of a template definition
of qsort().

					Tony Hansen
				att!pegasus!hansen, attmail!tony
				    hansen@pegasus.att.com
				      tony@attmail.com

jimad@microsoft.UUCP (Jim ADCOCK) (01/08/91)

ARM  pg 36 states that a pointer to any non-const, non-volatile object type
can be converted to a void*

ARM pg 69 states that a void* is considered a pointer to object type.

ARM pg 69 states that a B* can be converted to a D* if an unambiguous 
conversion exists and B is not a virtual base.

ARM pg 67 states that any conversion not mentioned on pgs 67 to 71 is
an error.

ARM pg 68 states that if you haven't yet defined a class, you can use
it in a pointer cast, in which case "no assumptions are made about class
lattices."  I leave it to the reader how a compiler performs such a cast 
without making any assumptions -- perhaps a random number is to be generated 
for the results of such a cast?

ARM pgs 67-71 do not otherwise cover possible explicit conversions from
a void* to a pointer to an object of some class.  Therefor, I claim
either:

1) Casts from a void* to a pointer to an object of some class already 
defined are in error.

or

2) The definitions given by ARM pgs 67-71 are in error.

and either:

3) No assumptions may be made about casts from a void* to a pointer to an
object of a class not yet defined.

or

4) The definitions given by ARM in these regards are in error.  

chip@tct.uucp (Chip Salzenberg) (01/09/91)

According to jimad@microsoft.UUCP (Jim ADCOCK):
>I claim either:
>
>1) Casts from a void* to a pointer to an object of some class already 
>defined are in error.
>or
>2) The definitions given by ARM pgs 67-71 are in error.

From the ARM, page 36 [commentary]:
"ANSI C allows an IMPLICIT conversion from a pointer to |void| to a
pointer to another object type (but not to a pointer to function type
...); in C++ a |void*| cannot be assigned to an object of any type
other than |void*| WITHOUT AN EXPLICIT CAST."  (Emphasis mine.)

So if you _do_ use an explicit cast, you may convert a |void*| to an
object of some class already defined.

If the text of the ARM does not explicitly support this point, then it
would seem there is an oversight in the text.
-- 
Chip Salzenberg at Teltronics/TCT     <chip@tct.uucp>, <uunet!pdn!tct!chip>
       "If Usenet exists, then what is its mailing address?"  -- me
             "c/o The Daily Planet, Metropolis."  -- Jeff Daiell

rfg@NCD.COM (Ron Guilmette) (01/13/91)

In article <465@mole-end.UUCP> mat@mole-end.UUCP (Mark A Terribile) writes:
+> >Both qsort() and bsearch() require a pointer to a function which [takes]
+> >two parameters of type pointer-to-void.  ... that the function ... will
+> >almost immediately cast [them to] pointer-to-T is utterly irrelevant.
+ 
...
+Second, the real conflict isn't between  void*  and  XYZ*  and it has nothing
+to do with MI.  NOTHING, NOTHING, NOTHING, NOTHING.  NO THING.  NOT A THING IN
+THIS WORLD.  NOTHING.  The MI problem occurs when you have pointers that are
+stored as  derived*  and interpreted as base* .  That problem *COULD* occur
+here, but it's not an essential part of the problem.  In other words, it's a
+herring that has gone beyond red and is well on its way to scarlet.

As you say, that problem can occur in the context under discussion, and
I think that it is a real problem and not a red herring.

+What is probably DISASTROUS is to use the ellipsis in declaring  qsort() .
+If  qsort() 's argument is of the form   int (*cmpr_fun)( ... )  the function
+may get called (and under some MS-DOS compilers, *WILL* get called) with an
+entirely different calling sequence, such that the function will misunderstand
+its arguments and even its call/return sequence.
...
+In other words, Sun Microsystems has indeed made a horrible mistake.

OK.  We are in agreement about that.  Doug L. ?  Where are you where we need
you?

Will this be fixed in a future release?

+Templates can provide a better solution...

Again, we agree.

+But templates don't answer the problem completely.  What happens if you
+have something like a database engine that has to handle whatever types and
+sizes are thrown at it without recompilation? ...

Perhaps such things are bad candidates for being coded in a quasi-
strongly-typed language like C++.

-- 

// Ron Guilmette  -  C++ Entomologist
// Internet: rfg@ncd.com      uucp: ...uunet!lupine!rfg
// Motto:  If it sticks, force it.  If it breaks, it needed replacing anyway.

rfg@NCD.COM (Ron Guilmette) (01/13/91)

In article <60328@microsoft.UUCP> jimad@microsoft.UUCP (Jim ADCOCK) writes:
>ARM pg 69 states that a void* is considered a pointer to object type.

I see no such statements anywhere in section 5.4.

Which ARM are you reading?

Anyway, if it did say that, it would not be entirely accurate.  A void*
type is actually a pointer to an *incomplete* object type.

-- 

// Ron Guilmette  -  C++ Entomologist
// Internet: rfg@ncd.com      uucp: ...uunet!lupine!rfg
// Motto:  If it sticks, force it.  If it breaks, it needed replacing anyway.

jimad@microsoft.UUCP (Jim ADCOCK) (01/15/91)

In article <278A3278.1241@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes:
|According to jimad@microsoft.UUCP (Jim ADCOCK):
|>I claim either:
|>
|>1) Casts from a void* to a pointer to an object of some class already 
|>defined are in error.
|>or
|>2) The definitions given by ARM pgs 67-71 are in error.
|
|From the ARM, page 36 [commentary]:
|"ANSI C allows an IMPLICIT conversion from a pointer to |void| to a
|pointer to another object type (but not to a pointer to function type
|...); in C++ a |void*| cannot be assigned to an object of any type
|other than |void*| WITHOUT AN EXPLICIT CAST."  (Emphasis mine.)
|
|So if you _do_ use an explicit cast, you may convert a |void*| to an
|object of some class already defined.

I disagree.  1) the text you quote is in the form of a prohibition.  
A prohibition cannot create permissions that do not otherwise 
exist.  2) the text you quote is a commentary, and thus not binding
anyway.

The commentary implies that one _might_ have some rights in some cases
to cast from void using explicit cast -- but does not imply where and
when and with what restrictions those rights might exist.

|If the text of the ARM does not explicitly support this point, then it
|would seem there is an oversight in the text.

Agreed that this is the most probable conclusion -- but, this conclusion
doesn't clarify what the oversight was, nor how the ansification 
[isofication?] committee will fix it.

mat@mole-end.UUCP (Mark A Terribile) (01/21/91)

> +Second, the real conflict isn't between  void*  and  XYZ*  and it has
> +nothing to do with MI. ...  ... it's a herring that has gone beyond red ...
 
> As you say, that problem can occur in the context under discussion, and
> I think that it is a real problem and not a red herring.

A real problem, yes.  To the question at hand?  A distraction, a false trail,
a wrong tree, a red herring.  The question at hand is interesting on its own.

 
> +But templates don't answer the problem completely.  What happens if you
> +have something like a database engine that has to handle whatever types and
> +sizes are thrown at it without recompilation? ...
> 
> Perhaps such things are bad candidates for being coded in a quasi-
> strongly-typed language like C++.

I think that *their datatypes* are poor candidates for incorporation into
the program.  But if you can write code to deal with arbitrary binary data
of abitrary size, and then can take the risk of it being, or not being,
valid object-bits when you look at it as an object, you can certainly code
it.

Really, the only language for which handling arbitrary blocks of data (and
sometimes trusting them to be a valid pointer or object or whatever) is not
a messy case to handle PROPERLY is assembler, and that's because everything
in assembler is messy; no single case is messy on its own.  OK, LISP will
do.  But LISP is an assembler too; an assembler for a hypothetical machine,
'tis true, but an assembler nonetheless.
-- 

 (This man's opinions are his own.)
 From mole-end				Mark Terribile

jimad@microsoft.UUCP (Jim ADCOCK) (01/29/91)

In article <1991Jan21.022720.20707@cbnewsk.att.com> hansen@pegasus.att.com (Tony L. Hansen) writes:
>I'll repeat:  The key to all of this is that whenever calling qsort(), you
>always have to pass it a function which casts from the const void* back to
>the true pointer that was passed in.  And once you do that, you can then cast
>to any base pointer you want!

I don't doubt that this should work on just about any sane compiler out
there today, _but_ I see nothing in ARM that gives one permission to 
explicitly cast back from a void* back to the original object* type.
What C++ does permit is casting back from a pointer to a   
smaller object.  I don't see anywhere ARM defines that the effective size
of a void object, were such allowed to exist, is smaller than any "real" object.
However, most all systems would make a char the smallest object, so one
_should_ be able to cast to/from a char*.  Thus, I claim if one needs
a generic object pointer, one should use a char*, not a void*.  If
you want to represent a generic address, but not imply an object is 
referred to, then feel free to use a void*.  Since section 5.4 does not
explicitly allow casting back from a void*, but most all compilers allow it,
I claim a cast back from void* is an un-constrained error -- IE a programming
error compilers are not required to diagnose.

jimad@microsoft.UUCP (Jim ADCOCK) (02/05/91)

In article <1991Feb3.195156.151@cbnewsk.att.com| hansen@pegasus.att.com (Tony L. Hansen) writes:
|Then it's a good thing that the ANSI C++ committee chose to use both the
|cfront 2.1 reference manual (which is essentially E&S) AND the ANSI C
|standard as their base documents.  Obviously there are a few things that are
|missing from the cfront 2.1 reference manual. If you use both documents,
|you'll find the necessary constraints. In fact, ANSI C (C S3.2.2.3) states
|explicitly:
|
|    A pointer to any incomplete or object type may be converted to a pointer
|    to void and back again; the result shall compare equal to the original
|    pointer.
|
|Another place (C S3.1.2.5) makes the guarantee that a void* and char* must
|have the same respresentation and alignment requirements.  Nothing in E&S
|eliminates these requirements.  E&S does explicitly add (C++ S5.4) that a
|void* is an object pointer.
|
|I think this should calm down anyone's fears that you can't use a void* as
|the "generic pointer". However, the ANSI C++ committee definitely needs to
|combine both sets of constraints into the standards document which eventually
|comes out.
|
|Anyone implementing a C++ compiler cannot just look at E&S. It's the
|combination of that and ANSI C which will be the eventual standard, not just
|one.

Hanson's position is that one can assume that any permissions granted by
ANSI-C not granted by ARM represents an omission from ARM.  I disagree.
The ANSI-C++ committee's stated position is that the ARM is the primary
source, and the ANSI-C document is secondary.  This raises the question
of whether differences between ARM and the ANSI-C document are intentional,
or accidental.  These differences will hopefully, someday, be resolved by
the ANSI-C++ committee.  Until then, someone wanting to write portable
C++ code would do well to only write code to the explicit permissions
contained in ARM.

There are already, well documented, well thought out, differences between
ANSI-C and C++ in regards to void pointers.  I see no reason to assume then,
in this case, that ANSI-C and C++ will be the same.  Again, I encourage
people to consider ARM and the working papers of the ANSI-C++ committee
to be the defacto definition of C++ until we get the final ANSI-C++ document
some years hence.  

rmartin@clear.com (Bob Martin) (02/07/91)

From: jimad@microsoft.UUCP (Jim ADCOCK)
>< [... some stuff deleted ...]
>< I see nothing in ARM that gives one permission to
>< explicitly cast back from a void* back to the original object* type.
>

If you refer to ARM section 4.6 there is an peculiar statement which reads:

	"in C++ a void* cannot be assigned to an object of any type other than
	a void* without an explicit cast."

If we remove the double negative it becomes:

	"in C++ a void* can be assigned to an object of any type other than
	a void* only with an explicit cast."

This is roundabout, but I think it refutes Jim's statement.

In article <1991Feb3.195156.151@cbnewsk.att.com> hansen@pegasus.att.com 
(Tony L. Hansen) writes:
[ ... lots of stuff about how ANSI C allows conversion of void* to any
other pointer type...  followed by this quote from ANSI C...]
>
>    A pointer to any incomplete or object type may be converted to a pointer
>    to void and back again; the result shall compare equal to the original
>    pointer.
>

Again in ARM 4.6 Stroustrup and Ellis argue against allowing this in C++
without explicit casts on the basis that it opens up a hole in the type
system.  

-- 
+-Robert C. Martin-----+:RRR:::CCC:M:::::M:| Nobody is responsible for |
| rmartin@clear.com    |:R::R:C::::M:M:M:M:| my words but me.  I want  |
| uunet!clrcom!rmartin |:RRR::C::::M::M::M:| all the credit, and all   |
+----------------------+:R::R::CCC:M:::::M:| the blame.  So there.     |

chip@tct.uucp (Chip Salzenberg) (02/13/91)

According to jimad@microsoft.UUCP (Jim ADCOCK):
>There are already, well documented, well thought out, differences between
>ANSI-C and C++ in regards to void pointers.  I see no reason to assume then,
>in this case, that ANSI-C and C++ will be the same.

But can anyone imagine a reasonable implementation on which the
conversion from |T*| to |void*| cannot be reversed?
-- 
Chip Salzenberg at Teltronics/TCT     <chip@tct.uucp>, <uunet!pdn!tct!chip>
 "I want to mention that my opinions whether real or not are MY opinions."
             -- the inevitable William "Billy" Steinmetz

jimad@microsoft.UUCP (Jim ADCOCK) (02/15/91)

In article <1991Feb6.222244.23132@clear.com> rmartin@clear.com (Bob Martin) writes:
|From: jimad@microsoft.UUCP (Jim ADCOCK)
|>< [... some stuff deleted ...]
|>< I see nothing in ARM that gives one permission to
|>< explicitly cast back from a void* back to the original object* type.
|>
|
|If you refer to ARM section 4.6 there is an peculiar statement which reads:
|
|	"in C++ a void* cannot be assigned to an object of any type other than
|	a void* without an explicit cast."
|
|If we remove the double negative it becomes:
|
|	"in C++ a void* can be assigned to an object of any type other than
|	a void* only with an explicit cast."
|
|This is roundabout, but I think it refutes Jim's statement.

I disagree.  Usually language references and standards consist of
explicit permissions and denials.  Only by taking all the explicit
permissions and denials in a language reference or standard can one
make sense of the authors intent.  When you invert the double negative,
you are attempting to turn an explicit denial into an implicit permission.
In this, I disagree.  Explicit denials cannot imply permissions -- they
always only restrict what a programmer is allowed to do.  If one could
imply implicit permissions from denial statements, you'd find the 
manual highly contradictory!

The original statement says that you cannot convert a pointer from a 
void* without an explicit cast -- it says nothing about where or when
such explicit casts are allowed.

Section 5.4 does explain what casts are permitted.  It also makes an
extrodinarily strong statement that any type conversion not mentioned
in that section and not explicitly defined by the user is an error!

Now the second paragraph on page 68 says that a pointer to one object
type may be explicitly converted to a pointer to another object type
[subject to other restrictions in section 5.4]  It also explicitly states
that a void* is a pointer to object type.  It also says that a pointer 
can be converted to an object of the same or smaller size and back again
without change.  Now a char* represents a pointer to the smallest object,
so a char* would seem to be preferred for such back casting.  Where are 
the "void" size and alignments specified?

The question that remains in my mind is exactly what is to be interpreted
from the "[subject to other restrictions in section 5.4]" statement --
given that it is already stated that anything not explicitly permitted
in 5.4 is denied, and given therefore that there *aren't* other restrictions
in section 5.4 -- since everything else is explicitly denied, the wording
of section 5.4 only consists of permissions.  What then, is the meaning of the
"[subject to other restrictions ....]" statement  ????

The bottom line is eventually one has to admit that the ARM is indeed
a reference manual, and not a standards specification.  Hopefully, all
these little details will be nailed down by the ansification effort.

hansen@pegasus.att.com (Tony L. Hansen) (02/16/91)

< From: jimad@microsoft.UUCP (Jim ADCOCK), Message-ID: <70450@microsoft.UUCP>
< Hansen's position is that one can assume that any permissions granted
< by ANSI-C not granted by ARM represents an omission from ARM.  I
< disagree.  The ANSI-C++ committee's stated position is that the ARM is
< the primary source, and the ANSI-C document is secondary.  This raises
< the question of whether differences between ARM and the ANSI-C document
< are intentional, or accidental.  These differences will hopefully,
< someday, be resolved by the ANSI-C++ committee.  Until then, someone
< wanting to write portable C++ code would do well to only write code to
< the explicit permissions contained in ARM.
<
< There are already, well documented, well thought out, differences
< between ANSI-C and C++ in regards to void pointers.  I see no reason to
< assume then, in this case, that ANSI-C and C++ will be the same.
< Again, I encourage people to consider ARM and the working papers of the
< ANSI-C++ committee to be the defacto definition of C++ until we get the
< final ANSI-C++ document some years hence.

< From: jimad@microsoft.UUCP (Jim ADCOCK), Message-ID: <70670@microsoft.UUCP>
< The bottom line is eventually one has to admit that the ARM is indeed a
< reference manual, and not a standards specification.  Hopefully, all
< these little details will be nailed down by the ansification effort.

There are definitely places where the ARM (and the cfront 2.1 reference
manual) is obviously not a a full standards specification. My understanding
of the ANSI C++ stand on ANSI C is that where the cfront 2.1 reference manual
(not the ARM) doesn't cover something, the ANSI C rule shines through. You
and I disagree as to whether the ANSI C rule should shine through in this
case. We definitely agree that the cfront 2.1 reference manual is
insufficient and that the final ANSI C++ standard must resolve the issue. All
current C++ compilers and common practice follow the ANSI C rule in this one
area. I hope that the next ANSI C++ draft (due out soon) will address this
issue and resolve this question.

(The following may be stating the obvious to some of you.) Note, as time goes
on, the ARM will inevitably become out of date. Reprints include corrections,
but pretty soon, the only true document which can be considered as any
statement of the current ANSI C++ position will be the current draft
standard.  Whether the ARM will continue to try to reflect the "current
draft" is a subject that Peggy and Bjarne will have to address. If it IS kept
up, then we'll be in the position of continually asking "which printing of
the ARM are you looking at?" If not, then we'll be in the same position that
we were in during ANSI C's development: people will continually have problems
gaining access to the current draft.

					Tony Hansen
				att!pegasus!hansen, attmail!tony
				    hansen@pegasus.att.com
				      tony@attmail.com

jimad@microsoft.UUCP (Jim ADCOCK) (02/20/91)

In article <27B94945.5977@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes:
|According to jimad@microsoft.UUCP (Jim ADCOCK):
|>There are already, well documented, well thought out, differences between
|>ANSI-C and C++ in regards to void pointers.  I see no reason to assume then,
|>in this case, that ANSI-C and C++ will be the same.
|
|But can anyone imagine a reasonable implementation on which the
|conversion from |T*| to |void*| cannot be reversed?

Yes, I can imagine such implementations.  If C++ is implemented on top
of "object-oriented" architectures, it may well be that pointers to 
primitive types have a totally different representation that pointers 
to "Object" types.  A void* might then be considered roughly equivalent
to a char*, or alternativelu a void* might be implemented simply as a mapping 
from a given pointer to a unique value -- it might be almost impossible to 
invert the mapping.

Systems using a Modula3-like approach might have T* be a traced pointer,
converting to a void* makes it untraced, converting back to a traced
pointer doesn't necessarily leave you in a happy state again!  Better to
not allow the inverse conversion then, lest the programmer falsely believe
the program is going to work....

So, I'd like to see the pointer conversions allowed on primitive types
be kept separate from the pointer conversions allowed on "Object" types.
This would not mean that C++ compilers on Un*x-like machines could not
support conversion to/from void* willy-nilly.  It would just mean that
portable code could not rely on such conversions if it is to run someday
on a non-Un*x-like machine.

Again, Rekusive is one example of a non-Un*x-like machine, that is designed
with the intent of supporting "Object Oriented" programming.  If people
read about it, perhaps we can get away from the Un*x-like prejudice that the
*only* way one can implement an architecture is as a very long array of
bytes.  The Cray and 80x86 machines offer two other "successful"
counter-examples.  Again, I claim that the very-long-array-of-bytes model
of CPU architectures doesn't have much to do with "object oriented programming."
Let's try to keep from being unnecessarily tied to overly-restrictive
CPU models.

chip@tct.uucp (Chip Salzenberg) (02/26/91)

According to jimad@microsoft.UUCP (Jim ADCOCK):
>If C++ is implemented on top of "object-oriented" architectures,
>it may well be that pointers to primitive types have a totally
>different representation that pointers to "Object" types.

This kind of distinction, while conceptually possible, would seem to
rule out a user's writing "operator new" and "operator delete", since
they generate or use pointers of type |void*| which subsequently will
be or previously have been used as "Object" addresses.

>A void* might then be considered roughly equivalent to a char* ...

Exactly equivalent, if compatibility with C libraries matters.

>...perhaps we can get away from the Un*x-like prejudice that the
>*only* way one can implement an architecture is as a very long array
>of bytes.

Please note that I am a long-time user of the '286 in large model, and
I am quite aware that a successful implementation of C or C++ does not
require a "very long array of bytes."  That issue, however, has almost
zero relevance to the convertability of a |void*|.

jimad@microsoft.UUCP (Jim ADCOCK) (02/28/91)

In article <27C95508.15D0@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes:
|According to jimad@microsoft.UUCP (Jim ADCOCK):
|>If C++ is implemented on top of "object-oriented" architectures,
|>it may well be that pointers to primitive types have a totally
|>different representation that pointers to "Object" types.
|
|This kind of distinction, while conceptually possible, would seem to
|rule out a user's writing "operator new" and "operator delete", since
|they generate or use pointers of type |void*| which subsequently will
|be or previously have been used as "Object" addresses.

No.  A C++ compiler in such a situation can accept definitions of
operator new and operator delete with the void*, but in generating
actual code return the true pointer type -- in actual application,
new returns a pointer to an actual object type, not a void*.  In the 
worse case schenerio, a C++ compiler might actually have to effectively
turn operator new into a template family of functions returning the
correct pointer types.  Or more likely, the compiler might have to generate two
different kinds of operator new -- one that returns a primitive pointer,
and one that returns an "Object" pointer.  But this is all doable, and
in no way violates the language.

|>A void* might then be considered roughly equivalent to a char* ...
|
|Exactly equivalent, if compatibility with C libraries matters.

Since when did C libraries use operator new and delete ???

|>...perhaps we can get away from the Un*x-like prejudice that the
|>*only* way one can implement an architecture is as a very long array
|>of bytes.
|
|Please note that I am a long-time user of the '286 in large model, and
|I am quite aware that a successful implementation of C or C++ does not
|require a "very long array of bytes."  That issue, however, has almost
|zero relevance to the convertability of a |void*|.

I disagree.  Somewhat similar to large model, an "object oriented"
CPU might go with a object#:member-inside-object# pointer representation.
Which is somewhat like a segment:offset representation.  A void* might
represent a conversion of such a object:member pair into an underlying
"hardwired" address of such a machine's random access memory.  Inverting
such an address back into object:member pair might be most prohibitive.

So, I believe people who support free-wheeling pointer-type hacking have
a Un*x-like processor model in mind.  They can't understand why some pointer
-type hacks *ought* to be prohibited -- since such pointer-type hacking is
*obviously* mearly conceptual.  *Obviously* it doesn't change any "bits"
in the pointer rep.

Its not *obvious* to me at all that we aren't signing C++ death warrent some
years down the line if "object-oriented" CPUs start catching on [like they
*ought* to :-]

Thus, I'd like to see C++ be pretty conservative about what kinds of 
type-hacking conforming compilers are *required* to support.  This in 
no way keeps un*x-like machines from supporting a greater variety of
type-hacking, if they so desire.