[comp.lang.c++] Smarter pointers - another solution

rfg@lupine.ncd.com (Ron Guilmette) (01/23/91)

If you have no interest in the issue of smart pointers (and how their
use could be made safer) then hit `n' now.

What follows is yet another proposal for a way to make the use of
smart pointers safer.  This bears little (if any) relationship to
my prior proposal(s).

This proposal has some notable advantages over others:  It requires
absolutely no syntactic changes to the language and only very minor
semantic changes.  As a matter of fact, it would actually CLARIFY
one aspect of semantics which is currently murky.  Compatability
with existing stocks of C++ code is (for the most part) maintained.

I welcome your comments.

(Note: Impatient readers may wish to skip directly down to the section
 labeled PROPOSAL SUMMARY.)

----------------------------------------------------------------------------
PROBLEM SUMMARY

When using so-called "smart pointers" for some controlled class C, the
programmer has no way to take complete control over the creation and use
of value of type T* and T& (i.e. "dumb pointers" and "dumb references"
to the class C).  This fact makes it difficult to insure that smart
pointers to some class type (T) are used safely and consistantly throughout
the program.

For example, one programmer may implement the type T along with code which
dynamically relocates objects of type T and then makes corresponding
adjustments to the set of all "smart pointers" to objects of type T.
Another example would be if one programmer implemented a type T along
with a separate smart pointer type for T such that when no more smart
pointers point at a given T object, that object would automatically be
destroyed.

In either of these cases, a different programmer (the "client") may
accidently make use of "dumb pointers" and/or "dumb references" to the
type T such that the dumb pointers and dumb references to type T objects
may (at some points in time) become invalid due to operations occuring
on the type T objects themselves.

If such invalid "dumb reference" or "dumb pointer" values are allow to
develop, and if they are subsequently used, run-time errors (which may
be quite difficult to diagnose or to correct) will probably ensue.

PROBLEM ANALYSIS

The fundamental problem is that the programmer cannot take complete control
over the creation and use of values of type T* and T& (due to the current
C++ language rules) unless he is willing to make the type T itself relatively
inaccessable; an alternative which may be unacceptable for other reasons.

In order to give the programmer complete control over the potential safety
problems which may be associated with uses of dumb T* and dumb T& values,
the language could either:

	o	make it impossible to use (i.e. operate upon) values of
		type T* or T& outside of certain limited areas of the
		program, or

	o	make it impossible to generate valid (non-null) values of
		type T* or T& outside of certain limited areas of the program.

This proposal considers the latter approach to the problem.  Specifically,
this proposal outlines a means for restricting valid non-null values of
type T* or T& to that region of the program which includes the member
functions of the class type T, its friends, and any classes derived
(either directly or indirectly) from the class type T.

There are five mechanisms by which valid non-null values of type T* or T&
(where the type T is a class type) may be generated.  These are:

	1)	A value of type T* may be generated by applying the unary
		& operator to a value of type T.

	2)	A value of type T* may be implicitly generated (by the
		compiler) for each call to a non-static member function
		of the class type T.  (This refers to the value that the
		compiler supplies for the `this' pointer.)

	3)	A value of type T* may be generated via the implicit
		application of the unary & operator to a value of type
		"array of T" (yielding the address of the zeroth element
		of the array).

	4)	A value of type T* (or T&) may be generated via an explicit
		or implicit cast from a different pointer type (or reference
		type) value (or an integral type value) to type T* (or T&).

	5)	A value of type T& may be generated via the initialization of
		a variable of type T& with an object of type T.

If the programmer wants to restrict the availability of valid non-null
values of type T* and T& to the scope of the members of class type T (as
described above) then he may partially implement such a restriction via
the following two steps:

	o	overload the unary operator& for the class type T, and

	o	write all public member functions (and operators, including
		operator&) of the class type T (and any member functions
		and operators of classes derived from T which override
		virtual members of T) such that they do not return (or
		otherwise yield) values of type T* or T& to the outside
		world.

If the programmer follows these steps, he will have taken control only of
mechanism 1 (listed above) by which valid non-null values of type T* and/or
T& may be generated (and subsequently used in unrestricted ways).

Currently, the C++ language rules do not provide the programmer with any
means of taking control of mechanisms 2, 3, 4, and 5 (listed above) by which
valid non-null values of type T* and/or T& might be generated outside of the
restricted context(s) mentioned earlier.


PROPOSAL SUMMARY

In order to provide the programmer with complete control over mechanisms 2,
3, 4, and 5 (listed above) it is proposed that all means by which values of
type T* or T& may be produced should be forced (by language rules) to invoke
one or the other of two "special" member functions (these functions being
`operator&' and the special type-conversion operator `operator T&') whenever
these operators are explicitly declared for a given class.

If language rules forced the invocation of either `operator&' or `operator T&'
for every case in which valid non-null values of type T* or T& (respectively)
could be produced, then then programmer could obtain complete control over
the availability of such values (to various parts of the program) simply by
controlling the visibility and accesability of these two special operators
(of type T).


PROPOSAL

The following new C++ language rules are proposed:

	o	Given a class type T for which a unary T::operator& is
		explicitly defined, the interpretation of an expression
		of type "array of T" (in all contexts) is that T::operator&
		is invoked (implicitly) for the zeroth element of the array.
		Thus, the type actually yielded by an expression of type
		"array of T" will be whatever type is yielded by T::operator&.

	o	Given a class type T for which a unary T::operator& is
		explicitly defined, the interpretation of each type
		conversion (either implicit or explicit) from any other
		type to a value of type T* is that the normal pointer
		conversion (to type T*) takes place and, following this,
		the member function T::operator& is invoked for the object
		pointed to by the converted pointer value (that is so say
		that the converted pointer value is passed into T::operator&
		as the `this' pointer).

	o	Given a class type T for which a user-defined type conversion
		operator `T::operator T&' is explicitly defined, the
		interpretation of each type conversion from any other type
		to a value of type T& is that the normal conversion (to type
		T&) takes place and, following this, the member function
		T::operator T& is invoked for the object refered to by the
		converted reference.

	o	The above rules have no effect upon the legality of the
		affected type conversions (i.e. such legality remains
		defined by other existing language rules) except that for
		any additional operator applications which are mandated by
		the rules above (i.e. either T::operator& or T::operator T&)
		the operator(s) involved must be both visable and accessable
		at the point of the conversion or else the conversion is
		illegal.

EXAMPLE

Here is a brief example of how the above rules could be used to create a
(controlled) type T and a "smart pointer" type for T.  In this example,
objects of type T are automatically deleted whenever there are no more
outstanding references to them.

	class smart_ptr_to_T;

	class T {
		int datum;
		int ref_count;
		operator T& ();				/* private */
	public:
		T (int data)
			{ datum = data; ref_count = 0; }
		~T ()
			{ if (ref_count > 0) panic (); }

		smart_ptr_to_T operator & ();	/* unary & */

		friend class smart_ptr_to_T;
	};

	class smart_ptr_to_T {
		T*	pointer;
		smart_ptr_to_T (T* arg)		/* private */
			{ pointer = arg; pointer->ref_count++; }
	public:
		smart_ptr_to_T (const smart_ptr_to_T &arg)
			{ pointer = arg.pointer; pointer->ref_count++; }
		~smart_ptr_to_T ()
			{ if (--pointer->ref_count == 0) delete pointer; }

		friend class T;
	};

	T::operator T& () { return *this; }	/* probably never called */

	smart_ptr_to_T T::operator & ()
	{
		smart_ptr_to_T return_val (this);

		return return_val;
	}

	T T_object (99);		/* OK */
	T& T_ref = T_object;		/* error: operator T& is private */
	smart_ptr_to_T p1 = &T_object;	/* OK */
	T* p2 = &T_object;		/* error: can't convert value to T* */


In this example, the classes `T' and `smart_pointer_to_T' are friends of one
another.  This simplifies the code and still prevents "outsiders" from knowing
too much about either type.

It is most important to note that `T::operator T&' is a private member of the
type T.  This makes it impossible to generate values of type T& outside of
the scope of members of T, and thus provides the programmer with a way to
insure that invalid references to objects of type T will not be "floating
around" the program.

PROPOSAL DISCUSSION

The most important aspect of this proposal is that its effect is limited
only to class types and only to those class types for which `operator&'
or `operator T&' are explicitly defined.  Such class types certainly
comprise only a small percentage of class types currently defined within
existing programs.  In fact, while it is likely that some existing programs
do contain classes for which `operator&' is explicitly defined, virtually
no existing programs currently contain classes for which an `operator T&'
(where T is the containing class type) is explicitly defined because the
semantics of defining `operator T&' are currently unspecified.

Also, those (rare) classes which explicitly define their own `operator&', 
almost always represent types for which associated "smart pointer" types
are also defined.  Certainly, The mere presence of an explicit definition
for `operator&' within a class clearly indicates the programmer's desire
to seize control over the production of pointers to that type.

This proposal simply adds to the amount of control which the programmer may
exercize in those few cases (and only in those few cases) where the programmer
clearly wants to take control.  Thus, it can safely be assumed that the
effects (on existing code) of adopting this proposal will be minimal and
that these effect (if any) will help rather than hurt existing code.

This idea is similar to tax reform.  The basic idea is to close all of
the existing loopholes.  With the current definition of the C++ language
there are a number of ways (described above) whereby values of "dumb
pointer" types and "dumb reference" types may be spontaneously generated
at various points throughout the program.  This proposal would give the
programmer the ability to restrict the generation of these (potentially
unsafe) pointers and references to only certain limited areas of his
program (in particular, to the pointed-at class, its members and friends).
Then, if problems arise with dumb pointers (or dumb references) getting
incorrect values, the programmer need only consider the code in the
pointed-at class, its members, and its friends, in order to find the
source of the invalid values.

Note that this proposal only provides the programmer with the ability to
control the *generation* of (potentially unsafe) dumb pointer and reference
type values.  It would still be the responsibility of the programmer to
insure that any such dumb pointer and/or dumb reference values which are
generated within the allowed contexts do not "leak out" into other areas
of the program.  It should be easy to exercize such control simply by
insuring that nothing in the pointed-at class (or in its friends or in
its derived classes) allows "dumb" (and potentially unsafe) pointer values
or reference values to "leak out" of the restricted area.

POTENTIAL PROBLEM AREAS

At first glance, there appear to be two potential problems with this
proposal.  As noted below, these "apparent" problems are not "actual"
problems.

The first apparent problem has to do with calling new() to allocate an
array of objects of some type T for which T::operator& is explicitly
defined.

Current language rules require that when new() is called to allocate
an array of objects of some class type T, the global operator new is
invoked rather than any class-specific operator T::new().  That would
be fine except for one thing.  The global operator ::new() is defined
to yield a value of type `void*' (see 12.5).  Note however that an
expression of the form:

	new T[n]

is defined to yield a value of type `T*'.  The implication is that for
such expressions, there is always an implicit conversion of the `void*'
value yielded by the global ::new() to a value of type `T*'.  This (quiet)
type conversion is always provided (implicitly) by the compiler in such
contexts.

Fortunately, the proposed new language rules given above cover this case.
The second of the four rules proposed above calls for "all" conversions
(either explicit or implicit) from any other type to a value of type T*
to automatically invoke `operator&' on the converted value (i.e. passing
the converted value into `operator&' as the `this' pointer).  This rule
would (necessarily) apply to the implicit compiler-supplied conversion
of the value returned by the actual body of the (global) new operator
(which must be of type void*) to the type T* (as required by the context
into which the value is returned).

Note that an interesting (and probably desirable) effect of these rules
would be that:

	smart_pointer_to_T p = new T[10];

would be legal (assuming that `operator&' were explicitly defined for the
type T, that it returned a type `smart_pointer_to_T' value, and that it
was both visible and accessible at the point where the above statement
appeared).

A second apparent problem with this proposal is more serious and may be
cause for more concern.

Under normal circumstances, default copy constructors and assignment
operators are defined (either explicitly or implicitly) for most classes.
The usual definitions of these member functions (for a class type T) assume
that they each take one argument of type `T&'.  In calls of these member
functions, the formal agruments (of type `T&') are initialized from the
actual arguments, which are usually of type `T'.

Unfortunately, if the new rules described above are adopted, then calls to
the default copy constructor and to the default assignment operator for
any class T which defines its own `T::operator T&' (and makes it private)
would become problematic outside of the class T itself (and its friends).
In fact, such calls would be illegal.

How then would one construct a T from another T (using the default copy
constructor) if one cannot even generate the T& value which is needed
as the argument to the constructor?  Likewise, how would one assign a
T to another T if one cannot even generate the required T& (from the
right hand operand of the =)?

As it turns out, calls to the default copy constructor or to the default
assignment operator would indeed become impossible (under the proposed
rules) outside of the class itself (and its friends) if `T::operator T&'
were declared as private to T.

This may initially appear to present severe problems, but in fact it is
quite consistant with the idea of limiting the availability of dumb
pointers and dumb references (outside of T).  If a T& could be created
(in an unrestricted way) from a T and then passed into a default copy
constructor, there is at least some chance that a signal would arrive
and would trigger the relocation (or even the destruction) of the
referenced objects while the copy constructor was executing!  That of
course is exactly the sort of thing that we would like to prevent when
we resort to "smart pointers".

The solution in such cases might not be pretty, but it would be completely
effective.  Simply put, if the user needs either default copy constructors
or default assignment operators to be globally available for a type T for
which `T::operator T&' has been declared private, the user would have to
explicitly define his own copy constructor and assignment operator and
these would each have to accept one argument of the "smart pointer" type
which is (uniquely) associated with the type T.  These explicitly defined
copy constructors and assignment operators could then be invoked, although
the invocations would look a bit odd:

	T object1;
	T object2 (&object1);

		... object1 = &object2;

Thus, this proposal does not creat any insurmountable problems with respect
to default copy constructors and default assignment operators (even if the
alternative approach used when 'T::operator T&' is private might offend our
artistic sensibilities).

kearns@softrue.UUCP (Steven Kearns) (01/24/91)

I am sure this must have been mentioned before, but I cannot
recollect the discussion, if there is one...

What is wrong with allowing one to define
"class T* {.....}"
which defines a new class that replaces the usual type (T *)?
Since this class is sort of implicitly named "T*", in the following
it will be convenient to use an explicit name: "Tpointer".  But this
is just an artifact of our discussion, and such a name never appears in
the program, or compiler.

Class Tpointer is a typical class, following the same rules as any
class.  The only difference is that anywhere in a program T* is
referred to, class Tpointer is the type actually referred to.

For example, the definition
	T * tp;
Would really be 
	Tpointer tp;  

Similarly, 
	int joe;
	T*tp = new T(joe);
is really 
	int joe;
	Tpointer tp = new T(joe);
which really calls the tp(T*) constructor and then the tp(tp&) copy
constructor.

This, by definition, makes it impossible for a programmer to get an
actual T* value, because anytime the programmer specifies such a
value, the class Tpointer will be the one actually referred to.

By symmetry, one would expect that we could do the same thing for
class T& {...}.  In reality, almost all applications will want class
T& to behave in all circumstances like a "dereferenced (T * const)"
would behave.  For example, 
	T myt;
	T& tr = myt;
	tr.t_operation(3);
is usually intended to mean:
	T myt;
	T* const tr = &myt;
	(*tr).t_operation(3);
This can be made explicit, perhaps, so that class T& would not have to
be declared after class *T was.

One potential criticism is that the implementors of class T* might
want to use the original "pointer to T" type.  But I believe that C++
already has a facility for this.  To declare foo as a true pointer to
T one could write "T ::operator* (foo)", and to dereference foo one
could write (foo.::operator*()).  Admittedly this is gross, but you
cannot have your cake and eat it too, and I think that it is much
better to make the class programmer type some more, than to have the
class user think some more.

Another potential criticism is that C++ should not redefine the meaning
of built in operators.  Well, where is that written in stone?  In the
case of pointers there are extremely good reasons to do so.

-steve


	
********************************************************
* Steven Kearns            ....uunet!softrue!kearns    *
* Software Truth           softrue!kearns@uunet.uu.net *
********************************************************

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

In article <3.UUL1.3#8618@softrue.UUCP> kearns@softrue.UUCP (Steven Kearns) writes:
|I am sure this must have been mentioned before, but I cannot
|recollect the discussion, if there is one...
|
|What is wrong with allowing one to define
|"class T* {.....}"
|which defines a new class that replaces the usual type (T *)?

Interesting idea.  How should a class T** work then?  As a (class T)**
or as a (class T*)* ?

Also, it would seem that the legal coercions of a (class T*)* would be
different from that of (class T)**

Still, I think this idea is worth thinking about.

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

In article <3454@lupine.NCD.COM> rfg@lupine.ncd.com (Ron Guilmette) writes:
|If you have no interest in the issue of smart pointers (and how their
|use could be made safer) then hit `n' now.
|
....
|When using so-called "smart pointers" for some controlled class C, the
|programmer has no way to take complete control over the creation and use
|of value of type T* and T& (i.e. "dumb pointers" and "dumb references"
|to the class C).  This fact makes it difficult to insure that smart
|pointers to some class type (T) are used safely and consistantly throughout
|the program.

My claim is that it is oxymoronic to try to make a "smart" version of a 
fundamentally "dumb" thing.  C++ pointers are fundamentally dumb things.
[C++ references aren't fundamentally dumb -- they're just crippled.]

C++ pointers are fundamentally dumb things because they are literally
designed to be "placemarkers" into uniform _arrays_ of things -- but instead
they are commonly used to reference _individual_ objects, and in C/C++
an individual object is not the same thing as an array of one object.

Thus, since I claim C/C++ pointers are fundamentally brain-dead, I would
encourage you to give up on them, and encourage you instead to think
about fixing the crippled aspects of C++ references, and the limitations
that keep one from creating smart references.

One obvious limitation that keeps one from easily creating a smart 
reference is the current restriction that one is not allowed to 
overload operator.() [operator dot] the way one can currently overload
operator->()

Another obvious limitation on C++ references is that they are always 
constant -- there is no way to re-assign to them after initialization,
since op= is pre-defined to mean "copy the referenced object."  Since the
obvious operators already have pre-defined meaning, allowing re-assignment of 
references in C++ would require new syntax: perhaps an op:= , for 
instance.


|There are five mechanisms by which valid non-null values of type T* or T&
|(where the type T is a class type) may be generated.  These are:
|
|	1)	A value of type T* may be generated by applying the unary
|		& operator to a value of type T.

As you point out, we already have control over this problem.

|	2)	A value of type T* may be implicitly generated (by the
|		compiler) for each call to a non-static member function
|		of the class type T.  (This refers to the value that the
|		compiler supplies for the `this' pointer.)

I claim "this" problem is the hard one to fix.  First, it gets into the
bowels of a compiler's stack and calling sequence generation.  Second,
member functions shouldn't have been defined to pass a "this" pointer to the
object invoked upon -- because the tacit assumption is that that object
is an element of a uniform array, and typically it is not.  Instead,
C++ should have been defined to pass a reference to "self."  "This" damage
being done, I don't see how to undo it, nor do I see an easy way for 
compilers to generate code based on generalized "smart this pointers."

As member functions currently can take a "const" on the implied
this pointer, perhaps you could add to the syntax where a "&" or 
"const &" imply that a "self reference" rather than a "this pointer"
is desired for a particular member function.  Perhaps you could generalize
this to where an exact "smart reference type" would be accepted where
the present "const" is allowed:

// use a smart reference instead of "this"
void Foo::doSomething() smart_Foo_reference;  

// use a plain "self" reference instead of a "this" pointer
void Foo::doSomething() &;

This is only the syntax, of course -- the code generation problem would 
remain.

|	3)	A value of type T* may be generated via the implicit
|		application of the unary & operator to a value of type
|		"array of T" (yielding the address of the zeroth element
|		of the array).

3) is one of the great type weaknesses of C/C++ IMO.  In use, a Foo[0..100]
gets automatically converted to a Foo[+-??? .. +-???], which the user typically
thinks of as just being a Foo.  When garbage collecting, given a Foo*,
what objects in Foo[0..100] are aliased [and thus kept alive] by the Foo*?
Explain your answer, and explain a reasonable way to generate code to 
handle this GC issue.

|	4)	A value of type T* (or T&) may be generated via an explicit
|		or implicit cast from a different pointer type (or reference
|		type) value (or an integral type value) to type T* (or T&).

More of the same problem.  Basically C++ allows a programmer to treat
a foo& the same as a foo[1] the same as foo[12] of foo[0..100] --
except in issues of optimization and memory management they are not at
all the same.

|	5)	A value of type T& may be generated via the initialization of
|		a variable of type T& with an object of type T.

	6) 	A value of type T* may be generated by adding or
		subtracting a variable of integral type to a type T*
		either directly, or via [] notation.

More of the same problems.

My conclusions:

Give up on pointers for serious C++ programming.  Enable the serious use
of references instead, including providing the facilities necessary for
smart references.  If a programmer needs an array of Foos, have the
programmer declare an array<Foo> and a smart_ref<array<Foo>> if an
alias for that array of Foos is needed.  Index into an arrayFoo using
arrayFoo[12] syntax, not using pointers.  The arrayFoo is now a distinct
object, held alive via a smart reference of the correct type, referring
to the start of the arrayFoo object.  What is aliased and thus to be
kept alive is now clear and unambiguous.

If people use references "everywhere" excepting when doing primitive
"C/asm" programming, the syntax of C++ becomes uniform:

	object.doSomething();

not:

	(*object).doSomething();
	object->doSomething;
	object.doSomething(); 
	object[0].doSomething();

....taken to the Nth power where N is the number of parameters and return
types.

Finally, even if one fixed the current problems with pointers and
references as mentioned above, one would only have an "enabling" 
solution, not a "supported" solution.  Creating classes that actually
use these smart references would still remain a tremendously difficult
task, that must be correctly performed in each derived class.  
If C++ added a mechanism whereby "smart references" would need to be
defined only once per inheritence hierarchy, and thereafter derived
classes would always "do the right thing"  -- only then could C++
be said to "support" smart references.

[Realistic people note all these problems, then say: "fat chance!" to
fixing them.  Personally, I'm less than completely realistic.]

rfg@NCD.COM (Ron Guilmette) (02/05/91)

In article <3.UUL1.3#8618@softrue.UUCP> kearns@softrue.UUCP (Steven Kearns) writes:
>I am sure this must have been mentioned before, but I cannot
>recollect the discussion, if there is one...
>
>What is wrong with allowing one to define
>"class T* {.....}"

This requires the language to be "mutable" (according to Stroustrup's
definition).  Stroustrup doesn't want to allow the language to be
"mutable" so this proposal is probably unacceptable.

Besides, if you were allowed to control all cases where values of the
type T* and T& could be generated (by providing your own definitions
of operator& and T::operator T&() as I described in my latest proposal)
then you would not need to add all of the syntactic extensions which
your proposal would entail.

-- 

// 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) (02/05/91)

In article <70279@microsoft.UUCP> jimad@microsoft.UUCP (Jim ADCOCK) writes:
>
>My conclusions:
>
>Give up on pointers for serious C++ programming...

Step right up folks.  Get your tickets to "Fantasy Island". :-)

>[Realistic people note all these problems, then say: "fat chance!" to
>fixing them.  Personally, I'm less than completely realistic.]

It's nice to know that Jim and I agree from time to time. :-)

-- 

// 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.

gintera@fsd.cpsc.ucalgary.ca (Andrew Ginter) (02/05/91)

jimad@microsoft.UUCP (Jim ADCOCK) writes:

> Thus, since I claim C/C++ pointers are fundamentally brain-dead, I
> would encourage you to give up on them, and encourage you instead to
> think about fixing the crippled aspects of C++ references, and the
> limitations that keep one from creating smart references.
> ...
> In use, a Foo[0..100] gets automatically converted to a 
> Foo[+-??? .. +-???], which the user typically thinks of as just being 
> a Foo.  When garbage collecting, given a Foo*, what objects in
> Foo[0..100] are aliased [and thus kept alive] by the Foo*?  Explain
> your answer, and explain a reasonable way to generate code to handle
> this GC issue.

As far as I understand it, your objection to pointers is mostly tied
up in the [], and pointer arithmetic operators.  Nobody says you have
to define these operators for a particular "smart pointer".

You also argue that the distinction between the "." and "->" operators
is a problem.  I argue that the distinction is completely syntactic.
The C++ langauge could easily be changed to replace all instances of
"->" with ".".  The only thing such a change would sacrifice is
compatibility with existing C++ and C programs.  The fact that you can
use "." with reference variables and not with pointers is not a big
deal with me.

Which objects in an array are kept alive by a pointer to an element in
the array?  Just as much of an object as is kept alive by a pointer to
a data member of the object.  All of the elements in the array (the
entire allocated block) are kept alive by any pointer to the interior
of the block.  All data members in an object are kept alive by a
pointer to the interior of the object.  Pointers (and reference
variables) are no more difficult for arrays than for any other object
accepting an interior pointer or reference.

Andrew Ginter, 403-220-6320, gintera@cpsc.ucalgary.ca