ngo@tammy.harvard.edu (Tom Ngo) (12/14/90)
$Revision: 1.6 $ 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 I posted the original form of this proposal on 12/11/90. In response to that posting, a member of the ANSI committee suggested that I extend the proposal to include member functions. So I canceled the original posting. If you still have it, please delete it and use this version instead. 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 usage of ~const were to be adopted, we would 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
schwartz@groucho.cs.psu.edu (Scott Schwartz) (12/15/90)
ngo@tammy.harvard.edu (Tom Ngo) writes:
If this usage of ~const were to be adopted, we would be obligated
to consider a similar type-specifier, ~volatile, ....
And maybe even ~alias, too. :-)
jbuck@galileo.berkeley.edu (Joe Buck) (12/16/90)
ngo@tammy.harvard.edu (Tom Ngo) writes: > If this usage of ~const were to be adopted, we would be obligated > to consider a similar type-specifier, ~volatile, .... No, we wouldn't. ~volatile wouldn't buy you anything. It would presumably be used to specify that a specific member in a volatile object is not volatile. While that would give a tiny bit of useful information to an optimizing compiler, it would gain nothing in expressive capability. I'm not sure I'm for the ~const feature, but the proposer does have an argument. I don't see the corresponding argument existing for ~volatile. You might want to include it anyway for consistency in the grammar. schwartz@groucho.cs.psu.edu (Scott Schwartz) writes: > And maybe even ~alias, too. :-) Cute. (For those that don't get the joke, this refers to the "noalias" keyword that was almost added by the ANSI C committee. -- Joe Buck jbuck@galileo.berkeley.edu {uunet,ucbvax}!galileo.berkeley.edu!jbuck
jimad@microsoft.UUCP (Jim ADCOCK) (12/28/90)
In article <NGO.90Dec14093655@tammy.harvard.edu> ngo@tammy.harvard.edu (Tom Ngo) writes about ~const [not-const] and ~volatile [not-volatile]. I like this idea, and suggest below further "flavours" that it should be possible to revoke or invert through negation. ---- I think ~const would be a good idea, iff we use this opportunity to clean up the problems with cast-from-const. Namely, if we have a supportive way to do caching "correctly" without cast-from-const, then we can take the opportunity to make cast-from-const a deprecated, strictly "implementation dependent" construct -- a construct which is not guaranteed to work as you intend in all situations on all compilers. This would allow optimizing compilers a chance to do meaningful optimizations on const member functions invoked on const objects, and would allow compilers to at least generate warnings on casts-from-const that appear to have little chance of working correctly. What I am suggesting then, is that the statement in 5.4 [regards an object cast from const] be changed slightly to: "The result of attempting to modify that [const] object through such a [non-const] pointer or reference is implementation dependent. Possible results include acting as if the object were originally declare non-constant, or addressing exceptions or other undesired behaviors." and the language in 7.1.6 be changed slightly to: "A const object of a type that does not have a constructor or a destructor might be placed in readonly memory. A const object that does have a constructor might be placed in discardable memory -- virtual memory that is only paged to disk the first time faulted out after construction. Therefor, the effect of any write operation to any const part of any such object is undefined." [constructed constant bit maps would be good candidates for such discardable memory] |[-] 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. IE in the typical example case where you have a const object except for one or two caching member variables, one could just write two intermediate classes -- one with non const fields, one with all fields const except the caching member varibles? Yuck. Or are you saying that today one is guaranteed to be able to cast from const? -- I claim not. This is another example of "enabling" verses "supporting." It seems that the issue of caching member variable has caused hassles often enough that C++ ought to support it, not just "enable" it. Having explicit support for non-const members within const objects then lets us clean up the issue of cast from const, and clearly label such as "implementation dependent" in all cases. Other examples of supporting verses enabling in C++ is MI support, verses having to explicitly linearize every method to combine the functionality of two classes. Or having overloadable operator dot to support delegation, smart references, etc, verses having to write a forwarding class that trivially re-derives all methods of a class. Or having access adjustments so that individual members can be adjusted from a default access, rather than having to specify the access of each member individually. | If this usage of ~const were to be adopted, we would be obligated | to consider a similar type-specifier, ~volatile, whose purpose is | to remove the volatile attribute from an unknown type. What's the status of const volatile? Does the ~'s then imply we also have ~const volatile, const ~volatile, ~const ~volatile .... ???? ....if const volatile is a relatively rare construct, then then inverting messiness above is not a big deal -- inverting a const volatile flavor is a rare action taken on an already rare construct. I support this ~const idea. I think it helps resolve a lot of the "does const mean const?" debates. ---- Further implication [in my mind] : If you can undo const and volatile flavors, maybe you ought to be able to undo other default flavors explicitly: ~virtual [not virtual] ~inline [not inline] In particular, then one could add a ~inline flavor to one particular method declared implicitly inline in a class declaration -- to aid debugging that one method. You wouldn't have to hack your class to move that one method being debugged from the .h file to the .c file. A complete list of keywords that I think could be possible candidates for negation is: ~const do allow this object to be changed after init. ~inline do make this method outline -- IE a real function call. ~register do not enregister this object, not even when optimizing [similar to volatile?] ~virtual do not put this method in the vtable. [keep the inherited vtable entry?] ~volatile do allow optimization on this object. Again, C++ does "enable" work arounds for all this negations. Typically the work around entails not specifying a flavor in a superclass, derive two subclasses, one with the flavor, and one without, etc. "Obviously", encapsulation and reusability are served if flavours specified in the superclass can be negated by sub-classes, without having to hack the super class. Comments?
pcg@cs.aber.ac.uk (Piercarlo Grandi) (01/03/91)
On 27 Dec 90 19:56:22 GMT, jimad@microsoft.UUCP (Jim ADCOCK) said: jimad> A complete list of keywords that I think could be possible candidates for jimad> negation is: jimad> [ ... ] jimad> ~inline do make this method outline -- IE a real function call. jimad> [ ... ] Let me home in on this. One problem of the current definition of 'inline' is that it specifies that every time the given function is called, it should be inlined. In other words, 'inline' currently means 'can inline, *must* inline'. The problem I see with this is that, for one of the two common reasons one wants to inline, in many cases expanding inline a function is irrelevant -- it is only productive in a few places, e.g. in inner loops, or where substantial opportunities for code merging exist. Inlining a function everywhere, unless it is very small, is just going to increase size with little benefit in speed. What is probably needed (not for C++ though) is a distinction between 'inlinable' and 'inline', where the first is used -- Piercarlo Grandi | ARPA: pcg%uk.ac.aber.cs@nsfnet-relay.ac.uk Dept of CS, UCW Aberystwyth | UUCP: ...!mcsun!ukc!aber-cs!pcg Penglais, Aberystwyth SY23 3BZ, UK | INET: pcg@cs.aber.ac.uk
pallas@eng.sun.com (Joseph Pallas) (01/05/91)
In <PCG.91Jan3145302@teachk.cs.aber.ac.uk> pcg@cs.aber.ac.uk (Piercarlo Grandi) writes: >One problem of the current definition of >'inline' is that it specifies that every time the given function is >called, it should be inlined. In other words, 'inline' currently means >'can inline, *must* inline'. This is entirely wrong. Unless the standard has changed the language or intent of the ARM, 'inline' is a hint, and nothing more. See p. 99 of E&S. Complain about particular implementations to their vendors. joe
jimad@microsoft.UUCP (Jim ADCOCK) (01/08/91)
In article <PCG.91Jan3145302@teachk.cs.aber.ac.uk> pcg@cs.aber.ac.uk (Piercarlo Grandi) writes: |On 27 Dec 90 19:56:22 GMT, jimad@microsoft.UUCP (Jim ADCOCK) said: | |jimad> A complete list of keywords that I think could be possible candidates for |jimad> negation is: | |jimad> [ ... ] |jimad> ~inline do make this method outline -- IE a real function call. |jimad> [ ... ] | |Let me home in on this. One problem of the current definition of |'inline' is that it specifies that every time the given function is |called, it should be inlined. In other words, 'inline' currently means |'can inline, *must* inline'. The problem I see with this is that, for |one of the two common reasons one wants to inline, in many cases |expanding inline a function is irrelevant -- it is only productive in a |few places, e.g. in inner loops, or where substantial opportunities for |code merging exist. Inlining a function everywhere, unless it is very |small, is just going to increase size with little benefit in speed. I disagree. I follow the suggested practice of using inline functions wherever possible where macros might have been used in the past. Inlines are preferable because they are more type safe, and provide better compile time error messages. If these functions are not inlined, my code will double or triple in size. Also, I use inline functions where the intent is that the compiler "customize" code for a particular application. In order for "customization" to happen, the code must be inlined, or again several times larger, slower code will occur. In my usage, fairly large inline functions are designed with the intent to generate a small amount of customized inline code, whereas a outlined, uncustomizable version of the code would be both much larger and much slower. However, these inlines can be a bear to debug. And I can't afford to make all my inline functions outline for debugging, since several times larger slower "uncustomized" code will result. Therefor, what I need is the ability to simply and easily specify a certain small set of functions to not be inlined. ~inline would be one such an approach. Another, less elegant approach would be to use some kind of #pragma not inline, or the such.
ksand@Apple.COM (Kent Sandvik) (01/08/91)
>|On 27 Dec 90 19:56:22 GMT, jimad@microsoft.UUCP (Jim ADCOCK) said: >|Let me home in on this. One problem of the current definition of >|'inline' is that it specifies that every time the given function is >|called, it should be inlined. In other words, 'inline' currently means >|'can inline, *must* inline'. The problem I see with this is that, for >|one of the two common reasons one wants to inline, in many cases >|expanding inline a function is irrelevant -- it is only productive in a >|few places, e.g. in inner loops, or where substantial opportunities for >|code merging exist. Inlining a function everywhere, unless it is very >|small, is just going to increase size with little benefit in speed. The danger with inlines and compiler optimization is not very high, mostly because non-inlined code suffers mostly concerning performance. A keyword like volatile in ANSI C is far more dangerous, because assumptions of register use could cause a lot of breakable, unportable code. So if a compiler does not do inline will not break code, but if the compiler does not follow the volatile keywords, then it would cause a lot of register-dependent code to fail. I hope standards creators take things like this into account concerning 'hint' keywords. Kent Sandvik -- Kent Sandvik, Apple Computer Inc, Developer Technical Support NET:ksand@apple.com, AppleLink: KSAND DISCLAIMER: Private mumbo-jumbo Zippy++ says: "C++, anything less is BCPL..."