Topic: RE-POST: ~const pre-proposal, Rev 1.8
Author: ngo@tammy.harvard.edu (Tom Ngo)
Date: 20 Feb 91 03:47:50 GMT Raw View
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