Topic: Suggested C++ enhancement


Author: daveg@synaptics.com (David Gillespie)
Date: 14 Jun 92 23:42:02 GMT
Raw View
I've been thinking about a couple of ideas for C++ enhancements, which
I'd like to bounce off you folks on the net.  The first one is a fairly
minor syntactic issue; the second one is a major new feature.

Apologies if these are FAQ's; they didn't seem to be.


I.  Omitting () in certain member function calls.

I find myself writing lots of C++ classes with simple member access
functions, along the lines of

 class String {
   int length;
 public:
   int len() const { return length; }
   ...
 };

 String s;
 int i = s.len();

The classic benefits for this are that the length is read-only to
the public, and that the implementation of the length can be changed
in the future without breaking code that uses the class.

I claim that member functions have the disadvantage that they force
the user to think in terms of function calls for things that "feel"
like simple properties, i.e., members of the class.  I want to be
able to write "int i = s.len" here, because that's how I think of
the length of the string.

Pascal lovers will advocate omitting () in all zero-argument function
calls; this would be a fairly radical change to the language and is
*not* what I'm asking for here.  Non-member functions, and member
functions with overt side effects on the object, would still have
parentheses to emphasize that they are performing actions rather
than just accessing data.  I want to be able to omit the parentheses
for member functions which are acting essentially like "smart"
variables.

The "&" notation would now be obligatory when the address of a member
function is taken.  Since pointers to member functions are relatively
new in the language, my guess is that this change would not break too
much existing code.  Is this a safe assumption?

The "overt side effects" requirement cannot be enforced as far as
I can tell.  It would be better to add a special syntax for member
functions which are callable without (), but I couldn't think of
a really good one.  With such a syntax, this would become a fully
backward-compatible extension to the language.  Any ideas?

Here's one possibility which might be okay:

 public:
   int len const { return length; }

where the parens are simply omitted, but only if an inline definition
is provided.  This would emphasize the idea that parentheses should
be optional only for simple variable-like accessor functions.
Naturally, if an accessor really needs a large definition then the
inline definition can simply call another old-style member function.


II.  Smart lvalues

I would like to take the "smart variable" idea one step further
and provide something analogous to "setf" methods in Common Lisp.
Here is a simple example:

 class String {
   int len;
   void validate();
 public:
   int len const { return length; }
   void operator len=(int x) { length = x; validate(); }
   String sub(int lo, int hi = -1) const;
   void operator sub=(const String &s, int lo, int hi = -1);
   ...
 };

 String s;
 s.len = s.len + 1;
 s.sub(1, 5) = "foo";

The "operator <identifier> =" notation creates a function which
acts syntactically like an address-less lvalue.  Effectively we
are overloading "len" based on whether it is being accessed or
changed.  Once again, I am allowing the parentheses to be omitted
in a function call with no arguments.

Many existing string classes define a "substring" function which
acts like a smart lvalue, which they implement by creating a new
"Substring" class.  Indeed, it would be possible to implement "len"
this way using another class, but it is unpleasant to have to
create a new class for every smart lvalue in an object.  I think
adding a convenient syntax like the one I propose would encourage
the use of the smart-lvalue idea, which in turn would lead to more
clean and concise code.

Note that the value being assigned is passed in as an extra first
argument to the smart-lvalue function.  It might make more sense
to put it last, but putting it first allows the function to have
optional arguments as I've done for "String::operator sub=".

Another example use would be a class which interfaces to an I/O
port, where accessing or setting a field turns into a read or
write operation on the port.  This would also be good for shared
data items, which can be accessed and set just like variables
but which use locking and semaphores behind the scenes.

Lvalues without addresses already exist in C++, in bit-fields.
In fact, smart lvalues would be able to implement bit-fields
by hand:

 class Thing {
   int flags;
 public:
   int ready { return flags & 1; }
   void operator ready=(int x) { flags &= ~1; flags |= !!x; }
 };

Note that I've used the smart-lvalue feature to have "ready" act
like a boolean, in the sense that storing any non-zero value in it
makes it "true."  Standard bit-fields generally take the low bit
of the stored value, which is a source of potential user errors
since it violates the usual C++ conventions for booleans.

I'm not sure what to do with "s.len++" and "s.sub(1,5) += s2".
Two possibilities I see are to have the compiler translate them
into "operator foo=" calls, or to allow additional functions like
"operator len++" and "operator sub+=" to be defined.  The latter
is more consistent with the way assignments are overloaded now.

What if there is an "operator foo=" defined for a class but no
plain "foo" member function?  The obvious answer is to allow "foo"
to be assigned to but not referenced as an rvalue (a "write-only"
member).  I don't know whether this is linguistically safe or not;
if not, it could simply be disallowed.

What if there is an "operator foo=" and also a "foo" which returns
a reference?  Again, it might be safest to disallow this.  If the
function is overloaded, I can imagine the programmer wanting to
handle some cases with a smart lvalue but other cases just by
returning a reference; I don't know if this convenience would be
worth all the complications it would introduce.  If smart lvalues
and references are never mixed, overloading can work the same as
always with no surprises.


So, what do you think?

        -- Dave
--
Dave Gillespie
  daveg@synaptics.com, uunet!synaptx!daveg
  or: daveg@csvax.cs.caltech.edu