[comp.std.c++] RE-POST: ~const pre-proposal, Rev 1.8

ngo@tammy.harvard.edu (Tom Ngo) (02/20/91)

In my earlier summary I promised to append the originally posted
proposal.  I forgot to do so, but here is a slightly later version.
Please delete any earlier versions you might have in your possession.
Sorry!

===========================================================================
$Revision: 1.8 $

I would like to propose an enhancement to C++.  This is in preparation
for submission to the ANSI committee.  The proposal is broken into
four short sections:

    1  Brief statement of the proposal
    2  More details about the proposal
    3  An example in which it would be useful
    4  Some debate over the proposal
    5  A possible extension about which I have reservations

BRIEF STATEMENT:

    I would propose a new specifier, ~const.  Only members of a class
    can be specified as ~const.  A data member that is specified as
    ~const can be modified even if the object of which it is a part is
    specified as const.  A member function that is specified as ~const
    can modify any data member even if the object for which it is
    called is specified as const.

    The primary purpose of the ~const specifier is to obviate some
    common cases in which the const attribute would otherwise have to
    be cast away.

MORE DETAILS:

1.  Following E&S 7.1.6 [Type Specifiers], a type-specifier can be:
	simple-type-name
	...
	const
	volatile
	~const
    The ~const specifier may appear in the type-specifier of a
    nonstatic, nonpointer member, whether it is data or a function.

2.  Following the same section, each element of a const array is
    const.  Each nonfunction, nonstatic, nonpointer member of a const
    class object is const unless it is specified as ~const.

3.  Following E&S 9.3.1 [The this Pointer]:

    A const member function may be called for const and non-const
    objects.  The type of this in such a member function of a class X
    is "const X *const".

    A non-const member function that is not specified as ~const may be
    called only for a non-const object.  The type of this in such a
    member function is "X *const".

    A member function that is specified as ~const may be called for
    const and non-const objects.  The type of this in either case is
    "X *const".

4.  Following E&S 8 [Declarators], a cv-qualifier can be:
	const
	volatile
	~const
    The ~const specifier may appear in the cv-qualifier of a pointer
    that is a nonstatic member.  And as usual, "The cv-qualifiers
    apply to the pointer and not to the object pointed to."  (E&S
    8.2.1.)

5.  The ~const specifier does not create any new distinct types.  It
    merely removes the const attribute.  Therefore the introduction of
    this specifier should require no new type matching rules.

EXAMPLE:

    The code below is intended to be short but self-contained.
    Consequently, it isn't the best example imaginable, but it
    illustrates the point.

    The class Angle holds a double whose sine and cosine are expected
    to be evaluated--possibly often, possibly not at all.  We
    presuppose the existence of a function sincos() which
    simultaneously computes the sine and cosine of a double in an
    efficient manner.  (Suns have such a function.)  The approach in
    this implementation of Angle is to cache the sine and cosine the
    first time either one is required.

    extern void sincos( const double x, double *const s, double *const c);
    class Angle {
      private:
	double x;
	~const double sinx, cosx;
	~const int cached;
	void cache(void) const
          { if (!cached) { sincos( x, &sinx, &cosx ); cached = 1; } }
      public:
	Angle(const double _x) : x(_x), cached(0), sinx(0), cosx(0) {}
	operator = (const double _x) { x = _x; cached = 0; }
	double sin() const { cache(); return sinx; }
	double cos() const { cache(); return cosx; }
    };

    The structure of which the class A is an abstraction (i.e. x) is
    not modified by the methods sin() and cos().  Therefore, it makes
    sense to declare those two methods const--the cache is an
    implementation detail that the user of Angle should not have to
    think about.

    Without the ~const specifier, one of two things must happen.
    Either cache(), sin() and cos() cannot be const--which, to me is
    unacceptable--or cache() must refer to cached, sinx and cosx
    through this, with const explicitly cast away.  With the ~const
    specifier, the cache() method is much easier to read.  In
    addition, someone reading the class declaration can tell right
    away that cached, sinx and cosx form some kind of cache that is
    transparent to the user.

    An alternative solution is to remove the ~const specifier from the
    data members sinx, cosx and cached, and to declare "void
    cache(void) ~const".  In my opinion this is less expressive since
    the user has to read and understand the code for Angle::cache() to
    find out that sinx, cosx and cached form a cache.

DEBATE:

Here, each idea is marked [+] or [-], depending on whether I think it
supports or detracts from the proposal.

[+] The semantics of ~const are simple:  ~const breaks the propagation
    of the const attribute from an aggregate or object to its
    components.

[-] The existing rule is even simpler: once you declare an aggregate
    or object const, its constness is guaranteed to propagate
    recursively to all of its subcomponents.  Similarly, once you
    declare a pointer const, the constness propagates to all
    subcomponents of its referent.  This proposal would represent the
    only violation of that rule.  [+] On the other hand, the
    applications of ~const are very real, and the only other way to
    implement them at the moment is by casting away const.  It would
    be better to choose the lesser of two evils.

[-] The existence of a ~const specifier might make the compiler's job
    somewhat more complicated.  While determining whether a function
    has side effects, an object that is declared "const" can no longer
    be assumed not to be modified.  [+] On the other hand, the ability
    to cast away const presents the same problem--and again, people
    will do one or the other (use ~const or cast away const).

    One might anticipate the complexity of handling the ~const
    specifier to be similar to the complexity of handling virtual
    functions.  With virtual functions, one must look at all base
    classes to determine whether a vtbl is needed for a class.  With
    ~const members, one must look at base classes and members that are
    objects to determine whether any ~const specifiers are present.

[+] The ~const specifier can remove some problems of the type alluded
    to in various places in E&S, most notably the end of section 5.4
    [Explicit Type Conversion].  The problem is that the effect of
    casting away const is implementation dependent.  At present, a
    compiler is free to place a const object in readonly memory.  With
    ~const, one might add the additional rule that (at least) ~const
    data members cannot be in readonly memory.

[-] In the hands of the wrong programmer, the ~const specifier
    could lead to sloppiness.  [+] On the other hand, all of C++
    relies on the neatness of the programmer.  For instance, a sloppy
    programmer might declare all members public and might never use
    "const" except where constrained to do so by existing libraries.
    This is no reason not to have those specifiers.

[-] Nothing can be done with the ~const specifier that can't
    already be done with the existing constructs.  [+] On the other
    hand, there are other things in C++ that are really just syntactic
    sugar.  For instance, there is really no difference between a
    reference "X&" and a constant pointer "X *const", besides the 
    obvious syntactic difference.

[+] The ~const specifier can become an idiom which expresses something
    that goes on naturally in the mind of a programmer.  I always make
    a distinction between those data members that are part of the
    abstraction represented by a class, and those that are not.  At
    present, the best I can do is use comments to mark the
    distinction.

[+] Adding a ~const specifier would introduce no new keywords, and
    would not break existing code.

POSSIBLE EXTENSION:

    The following item has been suggested to me, and I include it here
    not as part of this proposal, but as food for thought.  The idea
    ought to be explored, if only because it is a natural next step
    from the proposal as I have framed it.

    Caveats: this suggestion's usefulness depends on the existence of
    a language feature not mentioned in the ARM, and it opens a
    Pandora's box of related possibilities.

    I am told that a possible extension to C++ is to have run-time
    type information (e.g. "typeof").  Such a facility is presently
    included in gcc/g++, and the next few paragraphs use the syntax
    implemented therein.  Briefly, the typeof facility in gcc/g++
    permits referring to the type of an expression and can be used any
    place that a type name could normally be used.  Simple example:

        class Foo { ... };
        const Foo x;
        typeof (x) y;           // declares y to be a const Foo

    Now, the suggested extension to this proposal is to permit the use
    of ~const to remove the const attribute from an unknown type, as
    when x happens to be a macro parameter:

        #define nonconst(a) ~const typeof(a)
        nonconst(x) z;     // declares z to be a non-const Foo

    The semantics of ~const in this context are different from the
    semantics in my proposal.  Here, ~const removes the const
    attribute from the type that it modifies.  In my proposal, ~const
    breaks the propagation of the const attribute from an enclosing
    structure.  This difference in semantics leads to ambiguity if
    (referring to the example above) z happens to be a member of some
    class:

        class Bar {
            ~const typeof(x) z;
            ...
        };

    Do we mean for z to have the type of x, except with the const
    attribute removed?  Or do we mean for z to be modifiable even if
    the Bar object of which it is a part happens to be const?

    If this second usage of ~const were to be adopted, we might be
    obligated to consider a similar type-specifier, ~volatile, whose
    purpose is to remove the volatile attribute from an unknown type.
--
  Tom Ngo
  ngo@harvard.harvard.edu
  617/495-1768 lab number, leave message