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