Topic: Must derived class reserve space for an empty base class?


Author: jimad@microsoft.com (Jim Adcock)
Date: 24 Dec 92 00:36:04 GMT
Raw View
In article <5450@holden.lulea.trab.se> jbn@lulea.trab.se (Johan Bengtsson) writes:
|Jim Adcock (jimad@microsoft.com) wrote:
|: In article <5386@holden.lulea.trab.se> jbn@lulea.trab.se (Johan Bengtsson) writes:
|: |That two non-generic pointers of the same type (possibly converted from
|: |pointer-to-derived) to different objects or data members should be
|: |guaranteed to compare unequal.
|
|Your example (below) shows that at least we have established what we
|are disagreeing on.

No, it merely establishes one thing that we are disagreeing on.

|But the cases where "unnecessary" padding would be required would
|have very minor impact on the majority of programs, at least if I
|may consider the current implementation of virtuals (vtbl).  After all,
|inheriting from a truly empty base class (no virtuals) is quite uncommon,
|don't you think?

No.  Since the use of vtables and vtptrs is an implementation decision,
not a language decision, classes are "empty" if they have no member
objects, regardless of whether or not these classes include pure
virtual functions.  There is no requirement that implementatins put
vtptrs where you are assuming they go.  There is no requirement that
implementations even *have* vtptrs.

Given that the vtptr issue is an implementation decision, I'd say on
the contrary "empty" base classes are quite common.  As in pure virtual
base classes.

|If pointer distinctness (as defined above) allows useful semantic constructs,
|such as the very common idiom for operator=:
|"if ( &other == this ) return", then a small and rare storage overhead
|in examples such as the one below could easily be tolerated (IMHO).

Other people on the net have complained loudly, repeatedly, and
correctly [IMHO] about implementations that consume space when
inheriting from a pure virtual base classes.

|: If you want object addresses to be
|: unique, then write your code so that they are unique.
|
|You cannot always guarantee that.  Suppose you create a class template
|that uses pointers to an unknown class (T*) for identity tests (the
|template could be specified to handle only statically allocated objects,
|if you like).  That template cannot know that the type parameter (T)
|specifies a class that will have distinct pointers to all instances,
|_unless_ pointer distinctness is universally guaranteed.

Again you are trying to insist that your template class works for
every and all target types which is pure and simple nonsense.
Templates can and will always only work on target types that implement
not only the right syntax, but also the right semantics.  THE ONLY WAY
a template can know that the target type supports the right semantics
is for the template writer to SPECIFY the semantics, and for the template
invoker to CORRECTLY match such.  Otherwise all bets are off, even
if the syntax happens to match and the template compiles without
compile-time error.  Again, this is nothing new, and has nothing to
do with pointers.  If the template writer expects certain correspondences
between pointers and object identities, the template writer should specify
such, and the template invoker must correctly implement such for the
target type.  Again, this is nothing new.

|There are existing commercial class templates that rely on pointer
|distinctness.  Are they invalid?

Of course they're "valid" -- they're simply not strictly portable.
They meet the needs of a portion of the marketplace, and do not
meet the needs of others.  If they want to meet the needs of a greater
portion of the marketplace, then they need to write their classes
to rely on even fewer implementation dependences.

In practice "portability" is never an absolute, its only a relative
term.  In practice, no interesting program or library is "strictly
portable".  Interesting programs or libraries only have sections of
code that can be informally referred to as "portable" and other
sections of code that are clearly not strictly portable.  By
making things more portable the implementor makes porting the code
less expensive, while typically raising BOTH the cost of implementing
that code AND the cost of executing that code.   While we tend, as
a rule of thumb, think of more portable code as being better, in
reality such comparisons are only relatively true.




Author: jimad@microsoft.com (Jim Adcock)
Date: 24 Dec 92 20:23:39 GMT
Raw View
In article <85744@ut-emx.uucp> jamshid@ut-emx.uucp (Jamshid Afshar) writes:
|The fact the following idiom works under all current C++
|implementations, and that it will probably continue to work no matter
|what ANSI says, is what most worries me and why I originally posted
|this question.  Programmers will continue to do things like
| class Register {
|    Set<Register*> alive;
| protected:
|    Register() { alive += this; }
|    virtual ~Register() { alive -= this; }
|    static void foo();  // do something to all registered objects
| };
|
|If a special-case rule is not added making the above safe on *any* C++
|implementation, I hope more C++ literature will point out the fact
|that the above idiom is incorrect.

Given that you do not specify your implementation, such as telling us
what pointer comparisons you use or don't use, its hard to say
whether your example does or doesn't work under all current C++
implementations.  In the absence of evidence, I will hypothesize
the opposite, namely that your class DOESN'T work under all current
C++ implementations.

Below find an example that frequently catches programmers off guard.
Again, C++ pointers DO NOT model any notion of object identity.  If you
want your objects to maintain a notion of object identity [A good
idea, IMHO] then you must explicitly GIVE your objects a notion of object
identity.  C++ comes with no such notion built-in.

#include <iostream.h>

class A { char bsPadding; };
class B : public A {};
class C : public A {};
class D : public B, public C {};

main()
{
 D d;

 B* pb = &d;
 C* pc = &d;
 A* pa1 = pb;
 A* pa2 = pc;

 if (pa1 == pa2)
  cout << "pointers match\n";
 else
  cout << "pointers don't match -- even though of the same"
   " type and 'to the same object'!\n";

 return 0;
};





Author: jamshid@ut-emx.uucp (Jamshid Afshar)
Date: 26 Dec 92 07:31:46 GMT
Raw View
In article <1992Dec24.202339.26224@microsoft.com> jimad@microsoft.com (Jim Adcock) writes:
|In article <85744@ut-emx.uucp> jamshid@ut-emx.uucp (Jamshid Afshar) writes:
||[...] Programmers will continue to do things like
|| class Register {
||    Set<Register*> alive;
     ^woops, I forgot 'static'
|| protected:
||    Register() { alive += this; }
||    virtual ~Register() { alive -= this; }
||    static void foo();  // do something to all registered objects
|| };

|Given that you do not specify your implementation, such as telling us
|what pointer comparisons you use or don't use, its hard to say
|whether your example does or doesn't work under all current C++
|implementations.

I forgot the keyword 'static' in my example, but I thought the
intentions were obvious.  The problem with the above idiom is that
Register() could add the same 'Register*' twice (the second add being
ignored) when constructing a Register-derived object that contains
another Register-derived object as its first data member.

All current and probably future C++ compilers use a hidden vtbl
pointer to implement virtual functions so you probably won't encounter
any problems with the above idiom, but it is "wrong" unless rules are
changed.

|Below find an example that frequently catches programmers off guard.
|Again, C++ pointers DO NOT model any notion of object identity. [...]
|class A { char bsPadding; };
|class B : public A {};
|class C : public A {};
|class D : public B, public C {};
 A* pa1 = (B*)&d;    A* pa2 = (C*)&d;
 if (pa1==pa2)
|  cout << "pointers match\n";
| else
|  cout << "pointers don't match -- even though of the same"
|   " type and 'to the same object'!\n";

I know this and I don't have a problem with it.  There are two
different A objects (they would each have separate data members) so I
don't think they are pointers "to the same object".

Jamshid Afshar
jamshid@emx.utexas.edu





Author: bs@alice.att.com (Bjarne Stroustrup)
Date: 27 Dec 92 14:39:07 GMT
Raw View

jimad@microsoft.com (Jim Adcock @ Microsoft Corporation) writes

 > Below find an example that frequently catches programmers off guard.
 > Again, C++ pointers DO NOT model any notion of object identity.  If you
 > want your objects to maintain a notion of object identity [A good
 > idea, IMHO] then you must explicitly GIVE your objects a notion of object
 > identity.  C++ comes with no such notion built-in.
 >
 > #include <iostream.h>
 >
 > class A { char bsPadding; };
 > class B : public A {};
 > class C : public A {};
 > class D : public B, public C {};
 >
 > main()
 > {
 >  D d;
 >
 >  B* pb = &d;
 >  C* pc = &d;
 >  A* pa1 = pb;
 >  A* pa2 = pc;
 >
 >  if (pa1 == pa2)
 >   cout << "pointers match\n";
 >  else
 >   cout << "pointers don't match -- even though of the same"
 >    " type and 'to the same object'!\n";
 >
 >  return 0;
 > };

Huh? pa1 and pa2 does not point to the same object of type A. They point to
different sub-objects the D each of type A.

I conjecture that people who think that pa1 and pa2 should point to the
same object aren't confused about `object identity' but about the distinction
between virtual and ordinary base classes (and the reason for that is often
that they haven't read a good C++ textbook, but thought they could deduce
the meaning of the example from first principles or experience with some
other language).




Author: jimad@microsoft.com (Jim Adcock)
Date: 30 Dec 92 19:26:35 GMT
Raw View
In article <24490@alice.att.com> bs@alice.att.com (Bjarne Stroustrup) writes:
|jimad@microsoft.com (Jim Adcock @ Microsoft Corporation) writes
|
| > Below find an example that frequently catches programmers off guard.
| > Again, C++ pointers DO NOT model any notion of object identity.  If you
| > want your objects to maintain a notion of object identity [A good
| > idea, IMHO] then you must explicitly GIVE your objects a notion of object
| > identity.  C++ comes with no such notion built-in.
| >
| > #include <iostream.h>
| >
| > class A { char bsPadding; };
| > class B : public A {};
| > class C : public A {};
| > class D : public B, public C {};
| >
| > main()
| > {
| >  D d;
| >
| >  B* pb = &d;
| >  C* pc = &d;
| >  A* pa1 = pb;
| >  A* pa2 = pc;
| >
| >  if (pa1 == pa2)
| >   cout << "pointers match\n";
| >  else
| >   cout << "pointers don't match -- even though of the same"
| >    " type and 'to the same object'!\n";
| >
| >  return 0;
| > };
|
|Huh? pa1 and pa2 does not point to the same object of type A. They point to
|different sub-objects the D each of type A.
|
|I conjecture that people who think that pa1 and pa2 should point to the
|same object aren't confused about `object identity' but about the distinction
|between virtual and ordinary base classes (and the reason for that is often
|that they haven't read a good C++ textbook, but thought they could deduce
|the meaning of the example from first principles or experience with some
|other language).

I will counter-conjecture that people who consider this a trivial and
obvious example have not yet realized that pointers do not model object
identity in C++, in which case much of the arguments being held here
are moot.

Note that I said "'to the same object'" not "to the same object"



Author: larsn@Autodesk.COM (Lars Nyman)
Date: 18 Dec 92 18:47:33 GMT
Raw View
jimad@microsoft.com (Jim Adcock) writes:
|class A {};
|
|class B : public A
|{
|public:
| A a;
|};
|
|main()
|{
| B b;
| A* p1;
| A* p2;
|
| p1 = &b;
| p2 = &(b.a);
|
| if (p1 == p2)
|  printf("equal %lX %lX\n", (long)p1, (long)p2);
| else
|  printf("unequal %lX %LX\n", (long)p1, (long)p2);
|
| return 0;
|}

ARM p74:  "Two pointers to the same object compare equal".
ARM p163: "An object of a class consists of a (possibly empty) sequence of
    members".

I don't know if ARM defines what "same object" means.
But, it seems to me a member is a different (i.e. not the same) object than
the "enclosing object".

E.g. using the example above, 'b' is not the same object as 'b.a'.

Therefore, according to ARM p74-75, the pointer comparison in the example
above 'p1 == p2' (in effect '((A*) &b) == &b.a') is implementation dependent.

Atleast according to my reading of the ARM as it stands today...




Author: jamshid@ut-emx.uucp (Jamshid Afshar)
Date: 23 Dec 92 06:27:30 GMT
Raw View
In article <5450@holden.lulea.trab.se> jbn@lulea.trab.se (Johan Bengtsson) writes:
>Jim Adcock (jimad@microsoft.com) wrote:
>: On the contrary, requiring unnecessary and unexpected padding within
>: structures just to meet your new requirements would be extremely
>: undesirable, in my opinion.
>
>But the cases where "unnecessary" padding would be required would
>have very minor impact on the majority of programs, at least if I
>may consider the current implementation of virtuals (vtbl).  After all,
>inheriting from a truly empty base class (no virtuals) is quite uncommon,
>don't you think?

The fact the following idiom works under all current C++
implementations, and that it will probably continue to work no matter
what ANSI says, is what most worries me and why I originally posted
this question.  Programmers will continue to do things like
 class Register {
    Set<Register*> alive;
 protected:
    Register() { alive += this; }
    virtual ~Register() { alive -= this; }
    static void foo();  // do something to all registered objects
 };

If a special-case rule is not added making the above safe on *any* C++
implementation, I hope more C++ literature will point out the fact
that the above idiom is incorrect.  I've been doing stuff like this
for years and never gave it a second thought.  I guess the correct way
to do this would be to always add a dummy char to empty base classes,
but then that's a waste on existing implementations.  Besides, if we
are all going to do this, then why not instead make the compiler do it
-- it could do a much better job of determining when padding is
necessary.

>If pointer distinctness (as defined above) allows useful semantic constructs,
>such as the very common idiom for operator=:
>"if ( &other == this ) return", then a small and rare storage overhead
>in examples such as the one below could easily be tolerated (IMHO).

I hadn't thought about this situation.  I'm sure there are many other
cases besides operator=() where a member function wants to know if
'this' compares equal to the address of a parameter.  Yes, I know
there are many cases when the address of two distinct objects compare
equal when cast to |void*|, but as far as I know this is the only
situtation where the address of two distinct objects compare equal
without the aid of an eye-catching cast.

I'm still not sure how I feel about this situation.  I'm going to take
a closer look at my code after the holidays to see how often I
erroneously assume that when two EmptyAbstractClass*'s compare equal
they are both pointing to the same object.

Jamshid Afshar
jamshid@emx.utexas.edu




Author: jbn@lulea.trab.se (Johan Bengtsson)
Date: 15 Dec 92 19:24:49 GMT
Raw View
Jim Adcock (jimad@microsoft.com) wrote:
: In article <5367@miramon.lulea.trab.se> jbn@lulea.trab.se (Johan Bengtsson) writes:
: |Jim Adcock (jimad@microsoft.com) wrote:
: |[ Questionable C example involving compiler-generated conversion
: |  to void* deleted ]

: On the contrary, unquestionably conforming C example involving conversions
: to char*.

I thought ANSI C specifies void* to be the generic pointer type
instead of the previous char*.  Am I (and K&R2, 2nd Ed.) mistaken?

I still would call your example a degenerate case.  I still agree with you
that it would be gross to require pointer distinctness for generic pointers.

: |The fact that applying the adress operator (&) to a reference yields
: |a pointer to the referred-to object is irrelevant, since a reference
: |is just an alias for the referred-to object.  You cannot obtain
: |a pointer to a reference (as you already know).  This should not cause
: |any trouble.

We have been talking about _pointer_ distinctness (at least I hope we have),
not the result of applying the address operator (although they coincide,
except for references and perhaps arrays).  That is how I think we had
not been clear enough.

You were applying the address operator (&) to a reference-to-X (I don't
have your example available).  The result of that is not of type
pointer-to-reference-to-X, it is simply pointer-to-X, and is a pointer to
the referred-to object, not to the reference.

If pointer-distinctness is blessed, then it should be clarified that
taking the address of a reference is not regarded as a pointer to the
reference, but a pointer to the referred-to object (to me this is
already crystal clear, as defined in the ARM).

: |Perhaps with this clarification you can finally agree with us?

: I guess I don't understand what you are asking me to agree to?

That your examples need not be relevant to the discussion, or
even more preferably:

That two non-generic pointers of the same type (possibly converted from
pointer-to-derived) to different objects or data members should be
guaranteed to compare unequal.

Please? (:-)

--
--------------------------------------------------------------------------
| Johan Bengtsson, Telia Research AB, Aurorum 6, S-951 75 Lulea, Sweden  |
| Johan.Bengtsson@lulea.trab.se; Voice:(+46)92075471; Fax:(+46)92075490  |
--------------------------------------------------------------------------




Author: jimad@microsoft.com (Jim Adcock)
Date: 16 Dec 92 20:28:00 GMT
Raw View
In article <5386@holden.lulea.trab.se> jbn@lulea.trab.se (Johan Bengtsson) writes:
|That two non-generic pointers of the same type (possibly converted from
|pointer-to-derived) to different objects or data members should be
|guaranteed to compare unequal.
|
|Please? (:-)

Either you are misstating what you think you are stating, or I am
misunderstanding what you are stating, or else I strongly disagree,
because I would counterclaim that pointer equality in the following
example is a legal and perfectly reasonable, and efficient, implementation.
On the contrary, requiring unnecessary and unexpected padding within
structures just to meet your new requirements would be extremely
undesirable, in my opinion.  If you want object addresses to be
unique, then write your code so that they are unique.  Don't ask the
compiler to put the padding in for you.



#include <stdio.h>

class A {};

class B : public A
{
public:
 A a;
};

main()
{
 B b;
 A* p1;
 A* p2;

 p1 = &b;
 p2 = &(b.a);

 if (p1 == p2)
  printf("equal %lX %lX\n", (long)p1, (long)p2);
 else
  printf("unequal %lX %LX\n", (long)p1, (long)p2);

 return 0;
}




Author: jbn@lulea.trab.se (Johan Bengtsson)
Date: 18 Dec 92 15:41:08 GMT
Raw View
Jim Adcock (jimad@microsoft.com) wrote:
: In article <5386@holden.lulea.trab.se> jbn@lulea.trab.se (Johan Bengtsson) writes:
: |That two non-generic pointers of the same type (possibly converted from
: |pointer-to-derived) to different objects or data members should be
: |guaranteed to compare unequal.

Your example (below) shows that at least we have established what we
are disagreeing on.

[...]
[or else] I strongly disagree,
: because I would counterclaim that pointer equality in the following
: example is a legal and perfectly reasonable, and efficient, implementation.
: On the contrary, requiring unnecessary and unexpected padding within
: structures just to meet your new requirements would be extremely
: undesirable, in my opinion.

But the cases where "unnecessary" padding would be required would
have very minor impact on the majority of programs, at least if I
may consider the current implementation of virtuals (vtbl).  After all,
inheriting from a truly empty base class (no virtuals) is quite uncommon,
don't you think?

If pointer distinctness (as defined above) allows useful semantic constructs,
such as the very common idiom for operator=:
"if ( &other == this ) return", then a small and rare storage overhead
in examples such as the one below could easily be tolerated (IMHO).

: If you want object addresses to be
: unique, then write your code so that they are unique.

You cannot always guarantee that.  Suppose you create a class template
that uses pointers to an unknown class (T*) for identity tests (the
template could be specified to handle only statically allocated objects,
if you like).  That template cannot know that the type parameter (T)
specifies a class that will have distinct pointers to all instances,
_unless_ pointer distinctness is universally guaranteed.

Imagine the confusion that migh occur if you instantiated for example
a Lookup<B*> template class, using class B from your example (Lookup<>
is of course defined by a class/template library).

There are existing commercial class templates that rely on pointer
distinctness.  Are they invalid?

[ Jims example follows, press 'n' if you don't want to see it ]

: #include <stdio.h>

: class A {};

: class B : public A
: {
: public:
:  A a;
: };

: main()
: {
:  B b;
:  A* p1;
:  A* p2;

:  p1 = &b;
:  p2 = &(b.a);

:  if (p1 == p2)
:   printf("equal %lX %lX\n", (long)p1, (long)p2);
:  else
:   printf("unequal %lX %LX\n", (long)p1, (long)p2);

:  return 0;
: }

--
--------------------------------------------------------------------------
| Johan Bengtsson, Telia Research AB, Aurorum 6, S-951 75 Lulea, Sweden  |
| Johan.Bengtsson@lulea.trab.se; Voice:(+46)92075471; Fax:(+46)92075490  |
--------------------------------------------------------------------------




Author: jbn@lulea.trab.se (Johan Bengtsson)
Date: 12 Dec 92 17:23:39 GMT
Raw View
Jim Adcock (jimad@microsoft.com) wrote:
: jbn@lulea.trab.se (Johan Bengtsson) writes:
: |Jim Adcock (jimad@microsoft.com) wrote:
: |: steve@taumet.com (Steve Clamage) writes:
: |: |jimad@microsoft.com (Jim Adcock) writes:
: |: ||In "C" common implementation would make &g and &g.e identical,
: |: ||contradicting your assumptions.
: |: |
: |: |I don't this can happen in C, since you can't define a zero-sized object
: |: |or type.
[...]
: |: |On that basis, C compatibility would not seem to be an issue.
: |
: |: But the struct need not be empty for &g and &g.e to be identical.
: |
: |But it must, at least to produce two pointers of the same type that
: |compare equal.  Also, "g" and "g.e" must share a common superclass
: |for this to happen (within the type system).

: Below find legal C and C++ programs with non-empty structs where "g" and
: "g.e" compare equal.

Jim, we have obviously not been clear enough (me and Steve).  Yes,
the address of an object and its first member _cast to void*_ may
compare equal, so code handling void pointers must not assume
this kind of pointer distinctness.  However, neither "&g" nor "&g.e"
is of type void*.

[ Questionable C example involving compiler-generated conversion
  to void* deleted ]

The fact that applying the adress operator (&) to a reference yields
a pointer to the referred-to object is irrelevant, since a reference
is just an alias for the referred-to object.  You cannot obtain
a pointer to a reference (as you already know).  This should not cause
any trouble.

[ C++ example involving a reference data member that refers to *this deleted ]

Perhaps with this clarification you can finally agree with us?

--
--------------------------------------------------------------------------
| Johan Bengtsson, Telia Research AB, Aurorum 6, S-951 75 Lulea, Sweden  |
| Johan.Bengtsson@lulea.trab.se; Voice:(+46)92075471; Fax:(+46)92075490  |
--------------------------------------------------------------------------




Author: jimad@microsoft.com (Jim Adcock)
Date: 14 Dec 92 22:40:35 GMT
Raw View
In article <5367@miramon.lulea.trab.se> jbn@lulea.trab.se (Johan Bengtsson) writes:
|Jim Adcock (jimad@microsoft.com) wrote:
|[ Questionable C example involving compiler-generated conversion
|  to void* deleted ]

On the contrary, unquestionably conforming C example involving conversions
to char*.

|The fact that applying the adress operator (&) to a reference yields
|a pointer to the referred-to object is irrelevant, since a reference
|is just an alias for the referred-to object.  You cannot obtain
|a pointer to a reference (as you already know).  This should not cause
|any trouble.

Excuse me?

|Perhaps with this clarification you can finally agree with us?

I guess I don't understand what you are asking me to agree to?




Author: jimad@microsoft.com (Jim Adcock)
Date: 07 Dec 92 23:16:01 GMT
Raw View
In article <1992Dec3.194222.26871@taumet.com> steve@taumet.com (Steve Clamage) writes:
|jimad@microsoft.com (Jim Adcock) writes:
||In "C" common implementation would make &g and &g.e identical, contradicting
||your assumptions.
|
|I don't this can happen in C, since you can't define a zero-sized object
|or type.
|
|For example:
| struct X { }; /* illegal in C, but legal in C++ */
|
|On that basis, C compatibility would not seem to be an issue.

But the struct need not be empty for &g and &g.e to be identical.




Author: jbn@lulea.trab.se (Johan Bengtsson)
Date: 9 Dec 92 12:45:19 GMT
Raw View
Jim Adcock (jimad@microsoft.com) wrote:
: In article <1992Dec3.194222.26871@taumet.com> steve@taumet.com (Steve Clamage) writes:
: |jimad@microsoft.com (Jim Adcock) writes:
: ||In "C" common implementation would make &g and &g.e identical, contradicting
: ||your assumptions.
: |
: |I don't this can happen in C, since you can't define a zero-sized object
: |or type.
: |
: |For example:
: | struct X { }; /* illegal in C, but legal in C++ */
: |
: |On that basis, C compatibility would not seem to be an issue.

: But the struct need not be empty for &g and &g.e to be identical.

But it must, at least to produce two pointers of the same type that
compare equal.  Also, "g" and "g.e" must share a common superclass
for this to happen (within the type system).

(void*)(&g) == (void*)(&g.e) can of course always happen, regardless of
whether empty classes are involved.

--
--------------------------------------------------------------------------
| Johan Bengtsson, Telia Research AB, Aurorum 6, S-951 75 Lulea, Sweden  |
| Johan.Bengtsson@lulea.trab.se; Voice:(+46)92075471; Fax:(+46)92075490  |
--------------------------------------------------------------------------




Author: swf@tools3teradata.com (Stan Friesen)
Date: 9 Dec 92 23:38:55 GMT
Raw View
In article <1fdctnINN5o4@travis.csd.harris.com>, bill@amber.csd.harris.com (Bill Leonard) writes:
|>
|>    BobPlus  foo = {/* initial values for fields of BobPlus */} ;
|>
|> I got errors on some of the initial values telling me they were the wrong
|> type.  As far as I could see they were correct.  Finally, I discovered that
|> the compiler had "helpfully" inserted a dummy field in struct Bob so it
|> wouldn't be zero-sized.  So, unbeknownst to me, I needed an extra
|> initializer for the BobPlus fields to initialize this compiler-generated
|> field.

That is a bug in the compiler.  Even if it inserts a dummy field for you, it
*must* make sure it remains wholly invisible.  Any other treatment is a violation
of the programming model of the language.

Report the problem to your compiler vendor.

|> I personally think this is a bug.  Requiring struct Bob to be non-empty is
|> wrong.  Requiring *objects* to have a non-zero size is fine, but the
|> requirement should not extend to types. ...

Actually, it is perfectly legitimate to make Bob non-zero in all cases, as long
as only sizeof() and similar size-dependent operations can detect it.

The stuff inserted to make the size non-zero is *padding*, like used at the end of
C struct's to make them a multiple of the access boundry. A Bob should consist
entirely of padding - and thus have no *visible* fields.

--
sarima@teradata.com   (formerly tdatirv!sarima)
  or
Stanley.Friesen@ElSegundoCA.ncr.com




Author: jimad@microsoft.com (Jim Adcock)
Date: 10 Dec 92 19:44:45 GMT
Raw View
In article <5342@holden.lulea.trab.se> jbn@lulea.trab.se (Johan Bengtsson) writes:
|Jim Adcock (jimad@microsoft.com) wrote:
|: In article <1992Dec3.194222.26871@taumet.com> steve@taumet.com (Steve Clamage) writes:
|: |jimad@microsoft.com (Jim Adcock) writes:
|: ||In "C" common implementation would make &g and &g.e identical, contradicting
|: ||your assumptions.
|: |
|: |I don't this can happen in C, since you can't define a zero-sized object
|: |or type.
|: |
|: |For example:
|: | struct X { }; /* illegal in C, but legal in C++ */
|: |
|: |On that basis, C compatibility would not seem to be an issue.
|
|: But the struct need not be empty for &g and &g.e to be identical.
|
|But it must, at least to produce two pointers of the same type that
|compare equal.  Also, "g" and "g.e" must share a common superclass
|for this to happen (within the type system).

Below find legal C and C++ programs with non-empty structs where "g" and
"g.e" compare equal.  Note in the C++ case the comparison is strictly
conforming, the pointers compare equal, and g and g.e do not strictly
speaking share a common superclass.

==== C example ======


#include <stdio.h>

struct S
{
 char c;
};

main()
{
 struct S s;

 if (&s == &s.c)
  printf("equal %lX %lX\n", (long)(&s), (long)(&s.c));
 else
  printf("unequal %lX %lX\n", (long)(&s), (long)(&s.c));

 return 0;
}

==== C++ example ====


#include <stdio.h>

struct S
{
 S& rS;
 S() : rS(*this) {};
};

main()
{
 struct S s;

 if (&s == &s.rS)
  printf("equal %lX %lX\n", (long)(&s), (long)(&s.rS));
 else
  printf("unequal %lX %lX\n", (long)(&s), (long)(&s.rS));

 return 0;
}

========

While an implementation is not required to implement these structs in a
manner such that &g == &g.e my claim would be that the common case is
that compilers do typically so implement the structs.  All the compilers
I've worked with take the "equal" branch.




Author: jamshid@emx.cc.utexas.edu (Jamshid Afshar)
Date: 25 Nov 92 23:19:59 GMT
Raw View
There was discussion a while back about inheriting from an empty
class.  While an empty class must have a non-zero 'sizeof', I thought
it was said that a derived class does not have to reserve any space
for an empty base class.

 struct Empty {};
 struct D : public Empty { int i; }
 // a compiler could make sizeof(D)==sizeof(int)

But, that would mean that for

 struct G : public Empty {
    Empty e;
 };
 G g;

'&g' would be equal to '&g.e'.  Is that right?  I'm trying to think if
that would ever cause a problem.  Imagine if Empty kept a static list
of pointers to all its live objects -- might this list contain
duplicate elements?  What if Empty had a virtual function -- how would
it know which to call?  I know that under existing implementations
this isn't a problem since a class containing any virtual functions
also contains a vtable pointer, but are there bizarre, but legal,
schemes where this would be a problem?  This probably has other
problems, but couldn't a compiler maintain a |T*| => |vtable*| map for
each class T, instead of storing the |vtable*| in each T object.  This
wouldn't be more efficient, but would it be legal?

Thanks, Jamshid Afshar
jamshid@emx.utexas.edu





Author: jbn@lulea.trab.se (Johan Bengtsson)
Date: 26 Nov 92 13:07:04 GMT
Raw View
jamshid@emx.cc.utexas.edu (Jamshid Afshar) writes:
:
: There was discussion a while back about inheriting from an empty
: class.  While an empty class must have a non-zero 'sizeof', I thought
: it was said that a derived class does not have to reserve any space
: for an empty base class.
:
:  struct Empty {};
:  struct D : public Empty { int i; }
:  // a compiler could make sizeof(D)==sizeof(int)
:
: But, that would mean that for
:
:  struct G : public Empty {
:     Empty e;
:  };
:  G g;
:
: '&g' would be equal to '&g.e'.  Is that right?

Only if the compiler blindly optimizes away the space for the base class.
On machines where the minimum object size > 1, a smart compiler could
make &g.e == &g + 1 (char units).  With several Empty data members this
gets complex, so perhaps the compiler should simply avoid optimizing
away the space for adjacent empty bases classes and data members of
related types.

Note that the requirement is even stricter:

struct DerivedEmpty : public Empty { };
struct F : public Empty {
   DerivedEmpty e;
};
F f;

Here you need to avoid having (Empty*)(&f) == (Empty*)(&f.e).
I guess this kind of programming is unusual though.

: I'm trying to think if
: that would ever cause a problem.  Imagine if Empty kept a static list
: of pointers to all its live objects -- might this list contain
: duplicate elements?

I agree.  Also, this would make it difficult to use empty objects
as "abstract enumerators", distinguishable by their adresses.

: What if Empty had a virtual function -- how would
: it know which to call? I know that under existing implementations
: this isn't a problem since a class containing any virtual functions
: also contains a vtable pointer, but are there bizarre, but legal,
: schemes where this would be a problem?  This probably has other
: problems, but couldn't a compiler maintain a |T*| => |vtable*| map for
: each class T, instead of storing the |vtable*| in each T object.  This
: wouldn't be more efficient, but would it be legal?

As long as you can't observe different object behaviour, it must be legal.
The ARM does not require vtbl pointers in each object.
--
--------------------------------------------------------------------------
| Johan Bengtsson, Telia Research AB, Aurorum 6, S-951 75 Lulea, Sweden  |
| Johan.Bengtsson@lulea.trab.se; Voice:(+46)92075471; Fax:(+46)92075490  |
--------------------------------------------------------------------------




Author: steve@taumet.com (Steve Clamage)
Date: Sat, 28 Nov 1992 03:48:54 GMT
Raw View
jamshid@emx.cc.utexas.edu (Jamshid Afshar) writes:

|There was discussion a while back about inheriting from an empty
|class.  While an empty class must have a non-zero 'sizeof', I thought
|it was said that a derived class does not have to reserve any space
|for an empty base class.

| struct Empty {};
| struct D : public Empty { int i; }
| // a compiler could make sizeof(D)==sizeof(int)

|But, that would mean that for

| struct G : public Empty {
|    Empty e;
| };
| G g;

|'&g' would be equal to '&g.e'.  Is that right?

Right, in this case.

|I'm trying to think if that would ever cause a problem.

There are (at least) two classes of such problems.

1.  The user makes a non-portable assumption about the program.  If
the code requires that the base class and members of derived classes
be at different addresses, the code won't always work.  So don't make
non-portable assumptions in your code.

2.  Some aspect of the C++ implementation depends on class size being
non-zero.  In that case the implementation would use up some space for
the empty class.  (If not at first, then later, when bugs were reported.)

It is neither required nor forbidden than an empty base class use up
no space.  It is required that the C++ semantics be implemented.
--

Steve Clamage, TauMetric Corp, steve@taumet.com
Vice Chair, ANSI C++ Committee, X3J16




Author: jbn@lulea.trab.se (Johan Bengtsson)
Date: 29 Nov 92 21:39:43 GMT
Raw View
steve@taumet.com (Steve Clamage) writes:
: jamshid@emx.cc.utexas.edu (Jamshid Afshar) writes:
:
: |There was discussion a while back about inheriting from an empty
: |class.  While an empty class must have a non-zero 'sizeof', I thought
: |it was said that a derived class does not have to reserve any space
: |for an empty base class.
:
: | struct Empty {};
: | struct D : public Empty { int i; }
: | // a compiler could make sizeof(D)==sizeof(int)
:
: |But, that would mean that for
:
: | struct G : public Empty {
: |    Empty e;
: | };
: | G g;
:
: |'&g' would be equal to '&g.e'.  Is that right?
:
: Right, in this case.

Are you sure that this is allowed Steve?  It shouldn't (IMHO) be.
It is a nice property that objects and subobjects have distinct
addresses (if they are of the same type).

I would agree that the example is kind of twisted, but this
would be a special case in the language, where pointers to different
(sub)objects could compare equal.  Shouldn't special cases rather be
avoided?

: [...] If
: the code requires that the base class and members of derived classes
: be at different addresses, the code won't always work.

I would think many programmers might unwittingly assume just that.
Sorry, I have nothing substantial to back that claim, it's just
my feeling.

: It is neither required nor forbidden that an empty base class use up
: no space.

I think it is a nice optimization to make empty base classes take up no
space.  However, I think the compiler should not do it when the first
member is of the empty base class type (or any derived class).  Likewise
when two adjacent zero-size data members have a common base class.

Templates and the to-be-expected abundance of empty exception classes
makes this scenario more likely in the future (don't ask me to come
up with something specific though).
--
--------------------------------------------------------------------------
| Johan Bengtsson, Telia Research AB, Aurorum 6, S-951 75 Lulea, Sweden  |
| Johan.Bengtsson@lulea.trab.se; Voice:(+46)92075471; Fax:(+46)92075490  |
--------------------------------------------------------------------------




Author: bill@amber.csd.harris.com (Bill Leonard)
Date: 30 Nov 92 15:44:55 GMT
Raw View
In article <5275@holden.lulea.trab.se>, jbn@lulea.trab.se (Johan Bengtsson) writes:
> steve@taumet.com (Steve Clamage) writes:
> : jamshid@emx.cc.utexas.edu (Jamshid Afshar) writes:
> :
> : |There was discussion a while back about inheriting from an empty
> : |class.  While an empty class must have a non-zero 'sizeof', I thought
> : |it was said that a derived class does not have to reserve any space
> : |for an empty base class.
>
> I think it is a nice optimization to make empty base classes take up no
> space.  However, I think the compiler should not do it when the first
> member is of the empty base class type (or any derived class).  Likewise
> when two adjacent zero-size data members have a common base class.

I had a very confusing error the other day because of this, and it took
me hours to track down.  I had declared

   struct Bob {
   } ;

and later on declared

   struct BobPlus : public Bob {
      /* some fields here */
   } ;

   BobPlus  foo = {/* initial values for fields of BobPlus */} ;

I got errors on some of the initial values telling me they were the wrong
type.  As far as I could see they were correct.  Finally, I discovered that
the compiler had "helpfully" inserted a dummy field in struct Bob so it
wouldn't be zero-sized.  So, unbeknownst to me, I needed an extra
initializer for the BobPlus fields to initialize this compiler-generated
field.

Now how the H**l am I supposed to know what type this compiler-generated
field is going to be?  And how is someone else reading the initializers to
figure out that the first value belongs to a field in Bob that appears
nowhere in the code, but only in the compiler's mind?

The only way I could reasonably work around this was to make struct Bob be
non-empty myself, which really clutters up the code and the initializers to
no good purpose.

I personally think this is a bug.  Requiring struct Bob to be non-empty is
wrong.  Requiring *objects* to have a non-zero size is fine, but the
requirement should not extend to types.  I never intend to declare an
object of type Bob, only types derived from Bob.  In a sense, Bob is an
abstract base class; its sole purpose in life is to be used to declare
pointers to objects of different types.  (I explicitly cast the pointers to
the proper type when I use them.)

--
Bill Leonard
Harris Computer Systems Division
2101 W. Cypress Creek Road
Fort Lauderdale, FL  33309
bill@ssd.csd.harris.com
---------------------------------------------------------------------------
Prism: A place for light waves that commit minor refractions.
---------------------------------------------------------------------------




Author: steve@taumet.com (Steve Clamage)
Date: Mon, 30 Nov 1992 18:42:14 GMT
Raw View
jbn@lulea.trab.se (Johan Bengtsson) writes:

>steve@taumet.com (Steve Clamage) writes:
>: jamshid@emx.cc.utexas.edu (Jamshid Afshar) writes:

>: |But, that would mean that for
>:
>: | struct G : public Empty {
>: |    Empty e;
>: | };
>: | G g;
>:
>: |'&g' would be equal to '&g.e'.  Is that right?
>:
>: Right, in this case.

>Are you sure that this is allowed Steve?  It shouldn't (IMHO) be.
>It is a nice property that objects and subobjects have distinct
>addresses (if they are of the same type).

>I would agree that the example is kind of twisted, but this
>would be a special case in the language, where pointers to different
>(sub)objects could compare equal.  Shouldn't special cases rather be
>avoided?

At the moment, the language specification does not require that &g
and &g.e be distinct.

I think in general that relying on properties of class layout is poor
programming practice.  For example, you can't make any assumptions
at all about the relationship of addresses in two different access
sections (public, private) of the same class object.

Zero-sized class sub-objects are quite plausible in C++.  I don't see
why the language should require that such sub-objects take up space.
I also think it would be worse to try to say something along the lines
of your comment:
> However, I think the compiler should not do it when the first
> member is of the empty base class type (or any derived class).  Likewise
> when two adjacent zero-size data members have a common base class.

I think we must either leave it unspecified whether a sub-object must
take up space, or require that all sub-objects have non-zero size.

I think the latter is wasteful, since size 1 in practice will usually
mean size 4 or size 8 to allow alignment of what follows.  I favor
leaving it unspecified, since C++ hierarchies are not intended to
allow low-level specification of data layout.  (Classes may have hidden
data such as vtables.)

Perhaps someone has a compelling reason why no sub-object should have
size zero.
--

Steve Clamage, TauMetric Corp, steve@taumet.com
Vice Chair, ANSI C++ Committee, X3J16




Author: larsn@Autodesk.COM (Lars Nyman)
Date: 30 Nov 92 21:44:31 GMT
Raw View
steve@taumet.com (Steve Clamage) writes:
> >: jamshid@emx.cc.utexas.edu (Jamshid Afshar) writes:
>
> >: |But, that would mean that for
> >:
> >: | struct G : public Empty {
> >: |    Empty e;
> >: | };
> >: | G g;
> >:
> >: |'&g' would be equal to '&g.e'.  Is that right?
> >:
> >: Right, in this case.
>
> At the moment, the language specification does not require that &g
> and &g.e be distinct.
>
> ...
>
> Zero-sized class sub-objects are quite plausible in C++.  I don't see
> why the language should require that such sub-objects take up space.

But, using another example:

 class H {
    Empty e1;
    Empty e2;
 };

 H h;

and ARM p164:
 "Objects of an empty class have a nonzero size".
And in the embedded annotation that follows:
 "This implies that objects of an empty class can be allocated and
  have distinct addresses".

Then, the assertion:
 assert(&h.e1 != &h.e2);

must hold true (right ?).




Author: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Date: Tue, 1 Dec 1992 13:06:27 GMT
Raw View
In article <1fdctnINN5o4@travis.csd.harris.com> bill@amber.csd.harris.com (Bill Leonard) writes:
>> space.  However, I think the compiler should not do it when the first
>> member is of the empty base class type (or any derived class).  Likewise
>> when two adjacent zero-size data members have a common base class.
>
>I had a very confusing error the other day because of this, and it took
>me hours to track down.  I had declared
>
>   struct Bob {
>   } ;
>
>and later on declared
>
>   struct BobPlus : public Bob {
>      /* some fields here */
>   } ;
>
>   BobPlus  foo = {/* initial values for fields of BobPlus */} ;
>
>I got errors on some of the initial values telling me they were the wrong
>type.  As far as I could see they were correct.  Finally, I discovered that
>the compiler had "helpfully" inserted a dummy field in struct Bob so it
>wouldn't be zero-sized.  So, unbeknownst to me, I needed an extra
>initializer for the BobPlus fields to initialize this compiler-generated
>field.
>
>Now how the H**l am I supposed to know what type this compiler-generated
>field is going to be?  And how is someone else reading the initializers to
>figure out that the first value belongs to a field in Bob that appears
>nowhere in the code, but only in the compiler's mind?

 Your compiler is broken. It should not have allowed an initialiser
list AT ALL.

 ARM: p151: "An aggregate is .. an object of a class ... with
 no base classes ... When an aggregate is initialised  ...
 [you can use {} lists]"

BTW: it also says "with no constructors" which is not well defined
language. Does this mean no user defined constructors?
--
;----------------------------------------------------------------------
        JOHN (MAX) SKALLER,         maxtal@extro.ucc.su.oz.au
 Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------




Author: pkt@lpi.liant.com (Scott Turner)
Date: Tue, 1 Dec 1992 16:59:12 GMT
Raw View
In article <1992Nov30.184214.19236@taumet.com>, steve@taumet.com (Steve Clamage) writes:
|> >: jamshid@emx.cc.utexas.edu (Jamshid Afshar) writes:
|>
|> >: |But, that would mean that for
|> >:
|> >: | struct G : public Empty {
|> >: |    Empty e;
|> >: | };
|> >: | G g;
|>
|> At the moment, the language specification does not require that &g
|> and &g.e be distinct.

Even if "Empty" were not empty, one would be hard pressed to find that requirement in the current spec.

|> Zero-sized class sub-objects are quite plausible in C++.
|> Perhaps someone has a compelling reason why no sub-object should have
|> size zero.

I find Johan Bengtsson's reason compelling: it's a nice property for
distinct objects (including distinct subobjects) to have distinct addresses.
It is unfortunate that the spec only supports this for array elements.
--
Scott Turner
Liant Software Corp. (developers of LPI languages)
959 Concord St., Framingham, MA 01701 USA    (508) 872-8700
UUCP: uunet!lpi!pkt                          Internet: pkt@lpi.liant.com




Author: jimad@microsoft.com (Jim Adcock)
Date: 02 Dec 92 01:05:20 GMT
Raw View
In article <5275@holden.lulea.trab.se> jbn@lulea.trab.se (Johan Bengtsson) writes:
|: |'&g' would be equal to '&g.e'.  Is that right?
|Are you sure that this is allowed Steve?  It shouldn't (IMHO) be.
|It is a nice property that objects and subobjects have distinct
|addresses (if they are of the same type).

Since structs and classes can be the same thing, excepting default access
priviledges, previous implementations of struct layouts have to remain
legal in C++.  In fact, I'd argue that C++ programmers would for the most
part expect these issues to resolve the same as in "C".

In "C" common implementation would make &g and &g.e identical, contradicting
your assumptions.

Again, use of address as object identity in C++ is an extremely weak and
ill-defined concept.  If you don't like this, consider using surrogates
for object identity instead of addresses.




Author: mat@mole-end.matawan.nj.us
Date: Wed, 2 Dec 1992 17:40:06 GMT
Raw View
In article <1992Dec1.165912.15681@lpi.liant.com>, pkt@lpi.liant.com (Scott Turner) writes:
> In article <1992Nov30.184214.19236@taumet.com>, steve@taumet.com (Steve Clamage) writes:
> |> >: jamshid@emx.cc.utexas.edu (Jamshid Afshar) writes:

> |> >: |But, that would mean that for
> |> >:
> |> >: | struct G : public Empty {
> |> >: |    Empty e;
> |> >: | };
> |> >: | G g;

 . . .

> |> Zero-sized class sub-objects are quite plausible in C++.
> |> Perhaps someone has a compelling reason why no sub-object should have
> |> size zero.

> I find Johan Bengtsson's reason compelling: it's a nice property for
> distinct objects (including distinct subobjects) to have distinct addresses.
> It is unfortunate that the spec only supports this for array elements.

Scott, I would like to be able to create classes whose only purpose was
to disable compiler-defined assignment and copy constructors in derived
classes.  In fact, I'd like to see that as part of the standard library,
since it would help describe an Identity type (as opposed to an Rvalue type)
in a uniform way.  But every such base class wastes space in my complete
object.

I consider this a bug, or perhaps a BAD bug (Buggy As Designed).
--
 (This man's opinions are his own.)
 From mole-end    Mark Terribile

 mat@mole-end.matawan.nj.us, Somewhere in Matawan, NJ




Author: jbn@lulea.trab.se (Johan Bengtsson)
Date: 3 Dec 92 15:41:04 GMT
Raw View
jimad@microsoft.com (Jim Adcock) writes:
: In article <5275@holden.lulea.trab.se> jbn@lulea.trab.se (Johan Bengtsson) writes:
: |: |'&g' would be equal to '&g.e'.  Is that right?
: |Are you sure that this is allowed Steve?  It shouldn't (IMHO) be.
: |It is a nice property that objects and subobjects have distinct
: |addresses (if they are of the same type).
:
: Since structs and classes can be the same thing, excepting default access
: priviledges, previous implementations of struct layouts have to remain
: legal in C++.  In fact, I'd argue that C++ programmers would for the most
: part expect these issues to resolve the same as in "C".
:
: In "C" common implementation would make &g and &g.e identical, contradicting
: your assumptions.
:
: Again, use of address as object identity in C++ is an extremely weak and
: ill-defined concept.  If you don't like this, consider using surrogates
: for object identity instead of addresses.

Your points are well taken.  As I said, guaranteeing &g != &g.e would
be a nice property, since sometimes you would like to support object
identity without getting the overhead of a surrogate ID (the use should
be limited to statically and possibly persistently allocated objects).

I guess this won't happen as long as C keeps haunting the C++ language.

--
--------------------------------------------------------------------------
| Johan Bengtsson, Telia Research AB, Aurorum 6, S-951 75 Lulea, Sweden  |
| Johan.Bengtsson@lulea.trab.se; Voice:(+46)92075471; Fax:(+46)92075490  |
--------------------------------------------------------------------------




Author: steve@taumet.com (Steve Clamage)
Date: Thu, 3 Dec 1992 19:42:22 GMT
Raw View
jimad@microsoft.com (Jim Adcock) writes:

|In article <5275@holden.lulea.trab.se> jbn@lulea.trab.se (Johan Bengtsson) writes:
|>: |'&g' would be equal to '&g.e'.  Is that right?
|>Are you sure that this is allowed Steve?  It shouldn't (IMHO) be.
|>It is a nice property that objects and subobjects have distinct
|>addresses (if they are of the same type).

|Since structs and classes can be the same thing, excepting default access
|priviledges, previous implementations of struct layouts have to remain
|legal in C++.  In fact, I'd argue that C++ programmers would for the most
|part expect these issues to resolve the same as in "C".

|In "C" common implementation would make &g and &g.e identical, contradicting
|your assumptions.

I don't this can happen in C, since you can't define a zero-sized object
or type.

For example:
 struct X { }; /* illegal in C, but legal in C++ */

On that basis, C compatibility would not seem to be an issue.
--

Steve Clamage, TauMetric Corp, steve@taumet.com
Vice Chair, ANSI C++ Committee, X3J16




Author: pkt@lpi.liant.com (Scott Turner)
Date: Fri, 4 Dec 1992 20:05:31 GMT
Raw View
In article <1992Dec02.010520.27411@microsoft.com>, jimad@microsoft.com (Jim Adcock) writes:
|> In article <5275@holden.lulea.trab.se> jbn@lulea.trab.se (Johan Bengtsson) writes:
|> |It is a nice property that objects and subobjects have distinct
|> |addresses (if they are of the same type).
|>
|> In "C" common implementation would make &g and &g.e identical, contradicting
|> your assumptions.

In Bengtsson's example, &g and &g.e were comparable because g's type was
a derived class of g.e's type.  I don't think experience with "C" leads to
valid expectations regarding base class subobjects.

|> Again, use of address as object identity in C++ is an extremely weak and
|> ill-defined concept.

The standards committee should make it as well defined as it is in standard C,
if not better defined.  Beyond that, most of the weakness of the concept
relates to stateless objects.  I hope that weakness will be eliminated.

In article <1992Dec2.174006.4273@mole-end.matawan.nj.us> mat@mole-end.matawan.nj.us (Mark Terribile) writes:

|> Scott, I would like to be able to create classes whose only purpose was
|> to disable compiler-defined assignment and copy constructors in derived
|> classes.  But every such base class wastes space in my complete
|> object.

The standard should leave compilers with a lot of options for implementing
stateless subobjects.  Object identity can usually be supported without
using extra space, but a compiler would have to watch out for a case like
Bengtsson's.

It irks me to ponder an object-oriented language
which either has a crippled idea of object identity, or in which the
meaning of == and != for pointers is only very indirectly based on the
identity of the objects pointed to.
--
Scott Turner
Liant Software Corp. (developers of LPI languages)
959 Concord St., Framingham, MA 01701 USA    (508) 872-8700
UUCP: uunet!lpi!pkt                          Internet: pkt@lpi.liant.com




Author: jimad@microsoft.com (Jim Adcock)
Date: 05 Dec 92 01:29:17 GMT
Raw View
In article <5304@holden.lulea.trab.se> jbn@lulea.trab.se (Johan Bengtsson) writes:

>Your points are well taken.  As I said, guaranteeing &g != &g.e would
>be a nice property, since sometimes you would like to support object
>identity without getting the overhead of a surrogate ID (the use should
>be limited to statically and possibly persistently allocated objects).

If not surrogates consider the following:

Using standard tricks force a base class layout as follows:

+-------+
| vtptr |
+-------+
| xyz   |
+-------+

where "xzy" is some variable or variables of use to the base class.
Now make the "ID" of your objects the same as &xyz.  The "ID" of all
objects derived from this base class now have unique IDs.

So it's not too hard to come up with "reasonable" object IDs in C++ --
it's just that &some_object ain't it.





Author: fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON)
Date: Sun, 6 Dec 1992 12:15:21 GMT
Raw View
steve@taumet.com (Steve Clamage) writes:

>jimad@microsoft.com (Jim Adcock) writes:
>|Since structs and classes can be the same thing, excepting default access
>|priviledges, previous implementations of struct layouts have to remain
>|legal in C++.  In fact, I'd argue that C++ programmers would for the most
>|part expect these issues to resolve the same as in "C".
>
>|In "C" common implementation would make &g and &g.e identical, contradicting
>|your assumptions.
>
>I don't this can happen in C, since you can't define a zero-sized object
>or type.
>
>For example:
> struct X { }; /* illegal in C, but legal in C++ */

Is this indeed illegal, not just implementation-dependant?  I know of
several C compilers (GNU & Borland) that allow zero-sized objects or
sub-objects as an extension to ANSI C.

>On that basis, C compatibility would not seem to be an issue.

Given that there are several compilers that do allow zero-size objects or
sub-objects, and probably many programs that use this extension, I would
have disagree.

--
Fergus Henderson             fjh@munta.cs.mu.OZ.AU
This .signature virus is a self-referential statement that is true - but
you will only be able to consistently believe it if you copy it to your own
.signature file!




Author: jbn@lulea.trab.se (Johan Bengtsson)
Date: 6 Dec 92 18:28:30 GMT
Raw View
jimad@microsoft.com (Jim Adcock) writes:
: In article <5304@holden.lulea.trab.se> jbn@lulea.trab.se (Johan Bengtsson) writes:
:
: >Your points are well taken.  As I said, guaranteeing &g != &g.e would
: >be a nice property, since sometimes you would like to support object
: >identity without getting the overhead of a surrogate ID (the use should
: >be limited to statically and possibly persistently allocated objects).
:
: If not surrogates consider the following:
:
: Using standard tricks force a base class layout as follows:
:
: +-------+
: | vtptr |
: +-------+
: | xyz   |
: +-------+
:
: where "xzy" is some variable or variables of use to the base class.
: Now make the "ID" of your objects the same as &xyz.  The "ID" of all
: objects derived from this base class now have unique IDs.
:
: So it's not too hard to come up with "reasonable" object IDs in C++ --
: it's just that &some_object ain't it.

I don't see how this exercise buys you anything.  Why is &xyz a
more reasonable ID?  Since &xyz = &object + sizeof(data ptr),
this scheme should have exactly the same weaknesses as &object
alone.  If you make xyz a pointer to some structure that is
shared by copies of the same object, then I would agree that
"xyz" is a better ID than &object.

Besides, since the class above is non-empty, you know that
&object is distinct between objects, so it will certainly work
as ID for non-dynamic objects.

--
--------------------------------------------------------------------------
| Johan Bengtsson, Telia Research AB, Aurorum 6, S-951 75 Lulea, Sweden  |
| Johan.Bengtsson@lulea.trab.se; Voice:(+46)92075471; Fax:(+46)92075490  |
--------------------------------------------------------------------------




Author: steve@taumet.com (Steve Clamage)
Date: Mon, 7 Dec 1992 18:37:49 GMT
Raw View
fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON) writes:

>steve@taumet.com (Steve Clamage) writes:

>>I don't this can happen in C, since you can't define a zero-sized object
>>or type.
>>
>>For example:
>> struct X { }; /* illegal in C, but legal in C++ */

>Is this indeed illegal, not just implementation-dependant?  I know of
>several C compilers (GNU & Borland) that allow zero-sized objects or
>sub-objects as an extension to ANSI C.

ANSI section 3.2.1.5:
"A structure type describes a sequentially allocated nonempty set of
member objects..."

ANSI section 3.5.2.1 contains the syntax of a struct declaration,
which I will not reproduce here, but you cannot derive empty
braces from the syntax.  It does say in that section that a struct
with no named members has undefined behavior.  An empty struct
certainly has no named members.

I can't find a constraint that is violated, so I would say that no
diagnostic is required for an empty struct, and that it may be
allowed as an extension.

We don't have a C compatibility issue, however, because there are no
defined C semantics for empty structs.  No two C compilers need treat
them similarly, so you can't decide on that basis what should happen
in a C++ program.
--

Steve Clamage, TauMetric Corp, steve@taumet.com
Vice Chair, ANSI C++ Committee, X3J16