[comp.lang.c++] typeof

chip@tct.uucp (Chip Salzenberg) (03/29/91)

According to jjb@hardy.acs.washington.edu (Jim Black):
>In article <27EF838D.4115@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes:
>>Anything a type tag can do, a virtual function can do ...
>
>Try to snapshot an object, and bring it back to life in a different address
>space.  The virtual function table pointer is meaningless at that point,
>because it points into the wrong address space.  You want some way of 
>knowing the type of the object - SPECIFICALLY TO RESTORE THE VIRTUAL
>FUNCTION TABLE POINTER!  

I think we all agree that object freezing (translation of an object to
dead bytes) is essentially a non-problem, or at least a much simpler
problem than the issue Jim raises, namely, object thawing (translation
of dead bytes to an object).  So I will concentrate on the latter.

Rhetorical question: If an object is just dead bytes frozen in a file
or shared memory segment or whatever, how does the compiler know any
better than the programmer what the real type of the frozen object?

Answer: It hasn't a clue.  The language type system is inoperative on
dead arrays of bytes.  Neither typeof() nor any other type-specific
mechanism can do your work for you here.  You're on your own.

I continue, therefore, to maintain that a compiler-supported typeof()
function would be of little utility, and none for object thawing.

>I want to just snapshot the whole "new"'d range of memory and be able
>to reinstantiate it at a later time ...

What if the members are pointers or contain pointers?  You can't
seriously expect all persistent objects to be pointer-free!  (Talk
about not understanding the situation...)

>>If you want or need to change an argument in an overriding virtual
>>function, then it is obvious that you're not simply providing a new
>>implementation of the _same_ function.  ...  blame your design.
>
>class B {
>    operator <= (B&);
>};
>class D : B { 
>    operator <= (D&);
>};
>
>I dunno, call this bad design, but I don't think so.

Then we must disagree, because to me, this _is_ bad design!

You, as a human, may consider "<=" to be "the same function" in both
cases; but the compiler cannot make that leap.  To the compiler, the
first function implements "less than or equal to a B", and the second
function implements "less than or equal to a D" -- quite different!

Thus they are _not_ two implementations of the _same_ function, and
they should not be given the same name.
-- 
Chip Salzenberg at Teltronics/TCT     <chip@tct.uucp>, <uunet!pdn!tct!chip>
   "All this is conjecture of course, since I *only* post in the nude.
    Nothing comes between me and my t.b.  Nothing."   -- Bill Coderre

kelley@mpd.tandem.com (Michael Kelley) (03/30/91)

>According to jjb@hardy.acs.washington.edu (Jim Black):
>>In article <27EF838D.4115@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes:
>>>Anything a type tag can do, a virtual function can do ...
>>
>>Try to snapshot an object, and bring it back to life in a different address
>>space.  The virtual function table pointer is meaningless at that point,
>>because it points into the wrong address space.  You want some way of 
>>knowing the type of the object - SPECIFICALLY TO RESTORE THE VIRTUAL
>>FUNCTION TABLE POINTER!  
>
>I think we all agree that object freezing (translation of an object to
>dead bytes) is essentially a non-problem, or at least a much simpler
>problem than the issue Jim raises, namely, object thawing (translation
>of dead bytes to an object).  So I will concentrate on the latter.

Wait a minute; don't you say (five paragraphs down) that pointers within
objects present a real problem?  Wouldn't you agree it's in what you call
freezing, as well as thawing? (Aside: nifty new terms, Chip.)  How is it
that you then infer the original poster doesn't understand the situation?
Maybe he was posting *in order to understand*, but I'd imagine it doesn't
help him when you contradict yourself...

>
>Rhetorical question: If an object is just dead bytes frozen in a file
>or shared memory segment or whatever, how does the compiler know any
>better than the programmer what the real type of the frozen object?
>
>Answer: It hasn't a clue.  The language type system is inoperative on
>dead arrays of bytes.  Neither typeof() nor any other type-specific
>mechanism can do your work for you here.  You're on your own.
>
>I continue, therefore, to maintain that a compiler-supported typeof()
>function would be of little utility, and none for object thawing.

Whoa!  It's great that you understand the difference between support for 
functionality at the language level as opposed to support from a library.  
Obviously, ADA went too far in trying to do too much as a language.  But is
it realistic to completely decouple the language features from supporting
libraries?  Not here, I think.  (Just why do you think there is an ANSI-C++
library committee that works *with* the people responsible for the language?)
I'll go out on a limb and argue that in order to retrieve/activate/thaw 
an object, there MUST be a typeof() defined within the language in order to 
store/pacify/freeze *any* object and it's type.  Further, this typeof() must 
be able to determine equivalence between tags based on class hierarchy, unless 
you have either (1) dynamic loading or (2) no need for anything but exact type 
matching between a stored object and the object you use in memory.  If you 
have neither, then yes, you got me there, the semantics of your functions must 
be very well defined.

I think I'm with you in that reading an object back in is still a big hairy 
mess.  I think at a minimum, we'd need a special constructor (defined as part 
of the language), which just sets up virtual tables, and then calls a 
conversion routine to map "neutralized" data into some architecture-specific
format.  As an aside, I don't believe it's possible to make conversion a
part of the language (witness pointers and the like). But surely we
could agree on conversion routine names a little-better-than-brain-dead 
linker would know about so that a *standard* library could [de]neutralize *any*
object, independent of it's subclasses?  In case you're wondering, we use
a filter to sweep through object modules and libraries to look for magic
cookies generated by a smart cpp, which it uses to build a data structure
which encodes and provides access to our type hierarchy.  No macros, no
nothing, just as long as our superclass is an ancestor, it works great.
Given a complete hierarchy description, a deep copy is possible with no
programmer intervention...

I don't think persistance is a fad that's going to go away -- I think the
amount of traffic on the matter backs it up.  The time is coming, I think,
(those OODBMS people must be chomping at the bit!), when someone will come
up with a reasonably-priced persistance package.  Then, enough people will
use it, and whether a program's state information is determined through
persistance or on the fly will be a non-issue -- except that if your
program can't do so, or your external format is incompatible with theirs,
you may as well just go home.  And the other guy's/gal's code will look
so much like C++, you won't be able to defend yourself.

Bjarne must be both extremely pleased to see so much hoopla over his baby,
as well as frightened over all the "evil" plans we all have for it.  But 
frankly, knowing that C++ was expressly designed, in part, to be compatible 
with ANSI C indicates to me that other such pragmatic goals may in fact be 
more than acceptable to the "powers that be".

>
>>I want to just snapshot the whole "new"'d range of memory and be able
>>to reinstantiate it at a later time ...
>
>What if the members are pointers or contain pointers?  You can't
>seriously expect all persistent objects to be pointer-free!  (Talk
>about not understanding the situation...)
>
>>>If you want or need to change an argument in an overriding virtual
>>>function, then it is obvious that you're not simply providing a new
>>>implementation of the _same_ function.  ...  blame your design.
>>
>>class B {
>>    operator <= (B&);
>>};
>>class D : B { 
>>    operator <= (D&);
>>};
>>
>>I dunno, call this bad design, but I don't think so.
>
>Then we must disagree, because to me, this _is_ bad design!
>
>You, as a human, may consider "<=" to be "the same function" in both
>cases; but the compiler cannot make that leap.  To the compiler, the
>first function implements "less than or equal to a B", and the second
>function implements "less than or equal to a D" -- quite different!

Ah, ok. "..._is_ a bad design! ... You, as a human...".  Thanks for
sage words from the human compiler, Chip.  Now as I remember, the required
"feature" is called dispatching, which was rejected *not* because it's not
useful, or indicates a bad design, but because it's too doggone hard to
specify in a language, much less to implement in a (fast) compiler which
produces (fast and reasonably sized) code.


Mike Kelley
Tandem Computers, Austin, TX
kelley@mpd.tandem.com
(512) 244-8830 / Fax (512) 244-8247

chip@tct.com (Chip Salzenberg) (04/02/91)

According to kelley@mpd.tandem.com (Michael Kelley):
>According to chip@tct.com (Chip Salzenberg):
>>I think we all agree that object freezing (translation of an object to
>>dead bytes) is essentially a non-problem, or at least a much simpler
>>problem than the issue Jim raises, namely, object thawing (translation
>>of dead bytes to an object).  So I will concentrate on the latter.
>
>Wait a minute; don't you say (five paragraphs down) that pointers within
>objects present a real problem?

No, I did not say that.  I did say that pointers render impractical
the naive approach of "write(fd, p, sizeof(*p))".  But since that
naive approach is not used by anyone interested in portability, its
failure does not bode ill for other freezing methods.

>(Aside: nifty new terms, Chip.)

I'd like to take credit for them, but I'm sure they've been used
before... anyone care to prove me unoriginal?  [:-)]

>>The language type system is inoperative on dead arrays of bytes.
>
>Is it realistic to completely decouple the language features from supporting
>libraries?  Not here, I think.

There is no useful distinction to be made between a language feature
and a required library function.  Consider that ANSI C permits the
compiler to recognize standard functions and treat them specially,
such as by expanding them inline.  After all, if the language system
executes a program correctly, do we care if it uses a library routine
or a compiler feature for function X?  I think not.  So the issue of
language vs. library is irrelevant to this discussion.

>I'll go out on a limb and argue that in order to retrieve/activate/thaw
>an object, there MUST be a typeof() defined within the language in order
>to store/pacify/freeze *any* object and its type.

Sorry; typeof() is neither necessary nor sufficient.

Not necessary: because a virtual freeze_on() function for each type
can be written.  It's a bit of a pain, yes, but it can be done.

Not sufficient: because a simple_minded freeze_on() function that
iterates across member variables cannot deal intelligently with
pointers and references, especially for recursive data structures.

>I think I'm with you in that reading an object back in is still a big
>hairy mess.

No argument.  :-)  :-(

>I think at a minimum, we'd need a special constructor (defined as part 
>of the language), which just sets up virtual tables, and then calls a 
>conversion routine to map "neutralized" data into some architecture-specific
>format.

Nope.  Again because of the possibility of recursive data structures
and other tricks, thaw_from() *cannot* be written automatically,
unless the structures to be frozen and thawed are greatly restricted.
Given that fact, it's up to you, the programmer, to use whatever
convention suits you.  Perhaps a one-argument constructor that takes
an "istream &" will do.

>As an aside, I don't believe it's possible to make conversion a
>part of the language (witness pointers and the like).  But surely we
>could agree on conversion routine names a little-better-than-brain-dead 
>linker would know about so that a *standard* library could [de]neutralize
>*any* object, independent of it's subclasses?

The flaw in this plan is evident in the next paragraph:

>In case you're wondering, we use a filter to sweep through object modules...
>No macros, no nothing, just as long as our superclass is an ancestor ...
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

With a common ancestor, all things are possible.  The C++ language,
however, is based on a type forest, not a type tree; common ancestors
are the exception, not the rule.

>Now as I remember, the required "feature" is called dispatching ...

My comment about "B::foo(B&)" and "D::foo(D&)" not being the same
function is irrelevant to issues of dynamic dispatching.  It doesn't
matter _how_ you decide to execute "x.foo()".  B::foo() and D:foo()
implement different functions, and thus should be either non-virtual
or distinctly named.
-- 
Chip Salzenberg at Teltronics/TCT     <chip@tct.com>, <uunet!pdn!tct!chip>
   "All this is conjecture of course, since I *only* post in the nude.
    Nothing comes between me and my t.b.  Nothing."   -- Bill Coderre

kelley@mpd.tandem.com (Michael Kelley) (04/03/91)

In article <27F77834.16D6@tct.com>, chip@tct.com (Chip Salzenberg) writes:
> According to kelley@mpd.tandem.com (Michael Kelley):
> >
> >Is it realistic to completely decouple the language features from supporting
> >libraries?  Not here, I think.
> 
> There is no useful distinction to be made between a language feature
> and a required library function.  Consider that ANSI C permits the
> compiler to recognize standard functions and treat them specially,
> such as by expanding them inline.  After all, if the language system
> executes a program correctly, do we care if it uses a library routine
> or a compiler feature for function X?  I think not.  So the issue of
> language vs. library is irrelevant to this discussion.
> 
Yes, Chip, there is a distinction. If it's in the language, it (should be/is)
portable.  If it's not, you can only hope it's portable. Again, bare in
mind that I'm
NOT arguing to put every feature known to mankind in the language.  I like
it that C++ is as powerful as it is, yet is still pretty lean and mean.

> >I'll go out on a limb and argue that in order to retrieve/activate/thaw
> >an object, there MUST be a typeof() defined within the language in order
> >to store/pacify/freeze *any* object and its type.
> 
> Sorry; typeof() is neither necessary nor sufficient.
> 
> Not necessary: because a virtual freeze_on() function for each type
> can be written.  It's a bit of a pain, yes, but it can be done.

True, provided *you* implement the class.  What happens when you get a class
library from vendor X, that doesn't have a pure virtual freeze_on(), much less
any implementation for freezing/thawing?  Just write those objects off
as "forever temporal", rather than peristable.  With persistance, distribution
over a network is made vastly easier than it is now--and for us (and many 
others, I believe) the ability to distribute is a requirement.  
  
> 
> Not sufficient: because a simple_minded freeze_on() function that
> iterates across member variables cannot deal intelligently with
> pointers and references, especially for recursive data structures.
>

Rather than tightening up the language, as is done with one OODBMS I've
seen, I'd like to write static encoders/decoders for my class variables,
and let "someone  else" who understands hierarchy call them as needed.
Since the hierarchy information exists in the header files, in seems
natural for the compiler to give "someone else" the information. The
typeof() I refer to would be used to access this information, make
appropriate encoding/decoding call(s), tag the output when encoding, and
compare
tags when decoding.  If it's not part of the language, then it *must* be done
through a virtual function, using a common base class -- not viable if we hope
to use libraries written outside of our companies. I did say later that 
conversion can't be done in the language because of pointers, which you
did not see
relates to your previous point.  Oh well.
  
> >I think at a minimum, we'd need a special constructor (defined as part 
> >of the language), which just sets up virtual tables, and then calls a 
> >conversion routine to map "neutralized" data into some architecture-specific
> >format.
> 
> Nope.  Again because of the possibility of recursive data structures
> and other tricks, thaw_from() *cannot* be written automatically,
> unless the structures to be frozen and thawed are greatly restricted.
> Given that fact, it's up to you, the programmer, to use whatever
> convention suits you.  Perhaps a one-argument constructor that takes
> an "istream &" will do.

Again, I'm not arguing for generated conversion.  And yes, an istream&, or
an XDR*, will do just fine -- for me, for you, for everyone who's ever wrestled
with persistance.  But if we don't all do it the same way, we're not going to
be able to leverage off of each other's work. We'll have a forest of
objects that
can't talk to each other.

> 
> >As an aside, I don't believe it's possible to make conversion a
> >part of the language (witness pointers and the like).  But surely we
> >could agree on conversion routine names a little-better-than-brain-dead 
> >linker would know about so that a *standard* library could [de]neutralize
> >*any* object, independent of it's subclasses?
> 
> The flaw in this plan is evident in the next paragraph:
> 
> >In case you're wondering, we use a filter to sweep through object modules...
> >No macros, no nothing, just as long as our superclass is an ancestor ...
>                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 
> With a common ancestor, all things are possible.  The C++ language,
> however, is based on a type forest, not a type tree; common ancestors
> are the exception, not the rule.
> 

Exactly.  The operative phrase was "just as long as".  It works *only*
because we
have a common base class, and I don't like that it has to be that way *because*
it is the exception.  And I can't see any way to make it work for a
forest without
help from the language. As an aside, I'm well aware that there's a
serious problem
with class name clashes.

> >Now as I remember, the required "feature" is called dispatching ...
> 
> My comment about "B::foo(B&)" and "D::foo(D&)" not being the same
> function is irrelevant to issues of dynamic dispatching.  It doesn't
> matter _how_ you decide to execute "x.foo()".  B::foo() and D:foo()
> implement different functions, and thus should be either non-virtual
> or distinctly named.

Huh? I was addressing the invocation of x.foo(y), in keeping with the
prototypes
you put down.  What matters is that to invoke the right "x.foo()", I
don't have to know what type "x" is; but, to invoke the "x.foo(y)" I
want, *I've* got to tell the
compiler what type "y" really refers to -- the compiler will make a call
based on
*declared* type otherwise. Maybe I have it all wrong, but I think this
is exactly
what dispatching is all about.  Again, I realize this may be an
intractable problem within C++, but to write off requests for such
functionality as evidence of bad design is a serious mistake. I'm an
unabashed C++ fan, but am not so rabid to reject anything
that can't be done using the language as it is today. Some would say
"you should use
Objective-C or Smalltalk", but for my work, the advantage still goes to C++. 

Mike Kelley
Tandem Computers, Austin, TX
kelley@mpd.tandem.com
(512) 244-8830 / Fax (512) 244-8247

chip@tct.com (Chip Salzenberg) (04/04/91)

According to kelley@mpd.tandem.com (Michael Kelley):
>In article <27F77834.16D6@tct.com>, chip@tct.com (Chip Salzenberg) writes:
>> There is no useful distinction to be made between a language feature
>> and a required library function.
>
>Yes, Chip, there is a distinction. If it's in the language, it (should be/is)
>portable.  If it's not, you can only hope it's portable.

You missed the word "required", as in "required library function".
I stand by my quoted statement.

>> Sorry; typeof() is neither necessary nor sufficient.
>> 
>> Not necessary: because a virtual freeze_on() function for each type
>> can be written.  It's a bit of a pain, yes, but it can be done.
>
>True, provided *you* implement the class.  What happens when you get a class
>library from vendor X ...

This kind of problem is why I expect attempts at distribution of
binary-only C++ classes to have, at best, limited success.

C++ requires _everything_ about a class to be in one place, namely,
the class definition.  In particular, all member functions must be
declared there.  Forbidding users to change the class definitions is
too tight a straightjacket for me to impose on them.  (Remember that,
in current C++ implementations, adding virtual functions requires the
recompilation of all constructors.)

>> Not sufficient: because a simple_minded freeze_on() function that
>> iterates across member variables cannot deal intelligently with
>> pointers and references, especially for recursive data structures.
>
>Rather than tightening up the language, as is done with one OODBMS I've
>seen, I'd like to write static encoders/decoders for my class variables,
>and let "someone  else" who understands hierarchy call them as needed.

I would appreciate more detail on this proposal before making further
comment.  Please describe a possible implementation based on G++ or
cfront.

>But if we don't all do it the same way, we're not going to be able to
>leverage off of each other's work. We'll have a forest of objects that
>can't talk to each other.

I believe that the tree forest of C++ makes type incompatibility, or
the "can't talk to each other" phenomenon, inevitable.

>What matters is that to invoke the right "x.foo()", I don't have to know
>what type "x" is; but, to invoke the "x.foo(y)" I want, *I've* got to tell
>the compiler what type "y" really refers to ...

Understood.

>Again, I realize this may be an intractable problem within C++, but to
>write off requests for such functionality as evidence of bad design is
>a serious mistake.

To paraphrase The Tao Of Programming: A good design finds the simplest
harmony between idea and language.  In ARM C++, having two virtual
functions with the same name but different parameters _is_ a bad
design.  In ANSI C++, that may not be true any more; but I don't have
an ANSI compiler yet.  Do you?  :-)
-- 
Chip Salzenberg                    <chip@tct.com>, <uunet!pdn!tct!chip>
  Brand X Industries Custodial, Refurbishing and Containment Service
            When You Never, Ever Want To See It Again [tm]

lerman@stpstn.UUCP (Ken Lerman) (04/05/91)

I've been sitting on the sidelines listening to the fracas, but now I
think it might be useful for me to comment based on my experience with
Objective-C.

1 -- It is possible to save and restore arbitrarily complex graphs of
objects without explicit cooperation from each class.  This is done in
Objective-C.

2 -- It IS necessary (IMHO) that each object be able to identify
itself.  In Objective-C, this is done by having an instance variable
(called isa) which points to a class structure which contains the
name, the types of instance variables, ... , for that class.

The construction of that structure requires compiler support.

3 -- It is NOT necessary that each class in Objective-C have the same
root class, but it IS necessary that each have a first instance
variable which is the "isa" pointer.

4 -- I don't understand the aversion to having a single root class for
those objects which we wish to make persistent.  It seems to me that
multiple inheritence might be good for this.  (If it is not good for
this, what is it good for?)

If you believe our customers, the ability to save and restore object
graphs is valuable.  At least for applications written in Objective-C.

This is NOT intended to tell you to change your language to match
mine.  But if you want to add this functionality, experience with
Objective-C might be valuable.

Ken

chip@tct.com (Chip Salzenberg) (04/08/91)

According to lerman@stpstn.UUCP (Ken Lerman):
>1 -- It is possible to save and restore arbitrarily complex graphs of
>objects without explicit cooperation from each class.  This is done in
>Objective-C.

I find this claim hard to believe.  How does the freeze method know
whether a |char *| member points to allocated memory?  If it knows
that it is allocated, how does it know whether that memory should be
freed when the object is stored?  Such questions seem unsolveable
without cooperation from the class to be frozen.

>3 -- It is NOT necessary that each class in Objective-C have the same
>root class, but it IS necessary that each have a first instance
>variable which is the "isa" pointer.

The "isa" feature amounts to inheritance.  "They have a data member in
common, and they have these methods in common, but they're not derived
from a common base class.  Really!"  Sure.

>4 -- I don't understand the aversion to having a single root class for
>those objects which we wish to make persistent.

Nor do I.

>Multiple inheritence might be good for this.

I should hope so!
-- 
Brand X Industries Custodial, Refurbishing and Containment Service:
         When You Never, Ever Want To See It Again [tm]
     Chip Salzenberg   <chip@tct.com>, <uunet!pdn!tct!chip>