twagner@madrone.berkeley.edu (Tim Wagner) (08/10/90)
The recent discussion on this newsgroup about the conflicting uses of
'const' gives one pause. Shouldn't something as useful as the two
notions here be corrected in C++?
The key distinction is between a restriction of access through a particular
binding, and a restriction on access to an underlying object. In the
first case, the programmer merely wants to tighten the type of a name (binding)
to get additional compile-time error-checking for what he/she has decided is
an invalid access attempt. In the second case, the object being referred to
is truly a constant; the compiler is justified in placing it in read-only
storage, and preventing (to the extent possible) any non-read-only access
attempt through any binding at all.
For a name access restriction, casting into the equivalent non-restricted type
makes sense.
For object access restriction, casting into the non-restricted type should
produce a stern warning, since writing to such an object can produce runtime
errors depending on the implementation.
One problem that currently exists is the confusion between objects and
bindings. Consider the following code:
int i;
const int& r = i;
...
i = 5; /* Legal */
r = 6; /* Compiler warning about assigning to non-const object */
In the second line, the 'const' essentially refers to the binding of
the name 'r', NOT to the object 'r' refers to (which is obviously non-const).
In the following code, the 'const' refers to the object AND the binding:
const int i;
int& r = i;
...
i = 5; /* Compiler warning about assigning to non-const object */
r = 6; /* No warning, and probably a runtime error */
The confusion over the meaning of 'const' is troublesome, and creates
an asymmetry in whether it applies to the object or the binding. In
the latter case, one might very well desire a read-write object with
a read-only binding through i (but not through r). This is not expressible
in the language per se.
One possible solution is to apply machine-independent semantics for the
keyword volatile. That is, the following chart will obtain:
| volatile | none
-------------------------
const | A | B
-------------------------
none | C | D
-------------------------
A: A name declared with these specifiers has a read-only access restriction
associated with it (i.e., the name is read-only), but the underlying
object is read/write. Casts to B can occur implicitly; casts to
C or D must be explicit.
B: A name declared with only the 'const' specifier has both the read-only
access restriction, AND the object it refers to is declared to be
read-only.
The compiler is justified in replacing the value (where possible) with
its value (i.e., inlining the value), caching it in a register, etc.
Casts to A occur implicitly; casts to C or D must be explicit.
C: This suggests to the compiler that the object cannot be held in read-only
storage, even if it "believes" otherwise. There may be additional
optimizing
actions that are prevented by the addition of the "volatile" specifier.
Casts to D are implicit; casts to A and B must be explicit.
D: Normal type. Casts to A,B,and C are implicit. The compiler is free to
treat the object as read-only or read-write, depending on what sort of
analysis it is capable of. The binding, in any case, is read-write.
This has two advantages: it supplies a useful semantic meaning to 'volatile',
and it distinguishes between two important, but distinct, meanings of
read-only. gcc, for instance, will (in my mind correctly) handle a
global declaration of "const volatile int i;" by creating a read-write object
(i.e., not in the text segment) with read-only access restrictions for
the name 'i'. THIS SHOULD BE CODIFIED INTO LAW! Furthermore, a code fragment
such as:
int i;
const int *p = &i;
should be flagged as a violation: the semantics are inconsistent here, as
the object 'p' points to is NOT const. This should really be written
int i;
const volatile int *p = &i;
since the BINDING is read-only, rather than the object being pointed to.
The use of 'const' already confuses the issue of binding and object; the
suggested style requires 'const' to refer to the object and the binding, unless
'volatile' modifies it to refer to the binding only. Since this appears to
be the only place where bindings and objects are treated separately already,
the change in semantics appears justified. Furthermore, the use of
'volatile' to imply that the object is read/write is consistent with what
little semantics are described for it in E&S. If a particular site or
implementation requires additional semantics formerly produced by 'volatile',
a new keyword or pragma may be used instead.
*******************
Tim A. Wagner
Graduate Researcher, UCB
My opinions are my own, etc.
twagner@sequoia.Berkeley.EDU
*******************
sakkinen@tukki.jyu.fi (Markku Sakkinen) (08/10/90)
In article <26909@pasteur.Berkeley.EDU> twagner@madrone.berkeley.edu (Tim Wagner) writes: > >The recent discussion on this newsgroup about the conflicting uses of >'const' gives one pause. Shouldn't something as useful as the two >notions here be corrected in C++? > [rest of long article deleted] Sorry, I have not seen the previous discussion. In my ECOOP'88 paper I had a much simpler suggestion that would be sufficient for most practical cases. Rename "pointer to constant" as it currently stands in C++ into something like "nonmodifying pointer" and introduce also true pointers to constant. If we call ordinary pointers "modifying pointers", then obviously the only safe conversions are from "pointer to constant" to "nonmodifying pointer" and from "modifying pointer" to "nonmodifying pointer". References can be handled analogously to pointers, of course. Markku Sakkinen Department of Computer Science University of Jyvaskyla (a's with umlauts) Seminaarinkatu 15 SF-40100 Jyvaskyla (umlauts again) Finland SAKKINEN@FINJYU.bitnet (alternative network address)
mckenney@sparkyfs.istc.sri.com (Paul Mckenney) (08/10/90)
In article <26909@pasteur.Berkeley.EDU> twagner@madrone.berkeley.edu (Tim Wagner) writes: > [....] >One possible solution is to apply machine-independent semantics for the >keyword volatile. That is, the following chart will obtain: > | volatile | none >------------------------- >const | A | B >------------------------- >none | C | D >------------------------- >A: A name declared with these specifiers has a read-only access restriction > associated with it (i.e., the name is read-only), but the underlying > object is read/write. Casts to B can occur implicitly; casts to > C or D must be explicit. I disagree with this interpretation; the name declared might well refer to a register that is updated by hardware but that is not to be touched by at least part of the software. For example, the register might be part of a real-time clock, and writing to the register might have the effect of setting the clock -- not an operation to be performed lightly! In this case, the compiler should prohibit modifying the object referred to by the name, but must not assume that a copy of the object still has the same value as the object itself (since the object is subject to change by outside agents). Therefore, I feel that casts from A to all of B, C, and D should be explicit. Thanx, Paul
jimad@microsoft.UUCP (Jim ADCOCK) (08/14/90)
I think the idea of using 'volatile' with 'const' to help resolve some of const's multiple meanings is a good idea -- but I'm not sure the other suggestions follow. One problem I see is that there are [at least] two reasonable interpretations that one can put on 'volative' + 'const' 1) A read-only access permission to something that can be changing. A read-only access permission to a clock register, for example. One might not want to grant read/write permission to such a register because then anyone can change the time. 2) A statement that the internals of an object might change, but from the public interface to the object, the object looks unchanged. An example would be a routine that modifies cached values in an object, such that those values can be accessed faster in the future, but otherwise the state of the object appears unchanged from the outside world. This is the traditional case where people want to cast away from const in a routine whose parameters are declared const. One possibility is to overload the meaning of 'const' + 'volatile' depending on the order of the two: const volatile vs volatile const I think this word ordering is enough hair splitting to cause people a lot of trouble remembering, but yet I am loath to introduce yet-another key word. So I have mixed feelings about this. Restating, and expanding the original matrix, I get: A) volatile const. An object that appears const to the outside world that may be changed internally. An example might be an object that caches certain values for faster access in the future. B) const. A strictly read-only object that doesn't change. C) volatile. A read/write object that may change 'of its own accord.' This could include changes made via non-portable coding hacks. A compiler shouldn't apply optimizations that assume the object is going to stay the same. D) none. A read/write object that the compiler can apply reasonable optimi- zations to. The object doesn't change except via legitimate accesses within the language. A compiler can assume that non-portable coding hacks are not being used. E) const volatile. Read-only permissions to an object that may be changing of its own accord. A example might be a clock register to which write permissions need to be denied, because people shouldn't be allowed to change the time. Compilers can't optimize. "Safe" conversions that could be applied implicitly are: D to B, C, E, A B to E C to A, E A to C, E E to none-of-the-above
gregk@cbnewsm.att.com (gregory.p.kochanski) (08/14/90)
AARGH! One might as well use const(1) const(2) const(3) ... rather than trying to express these things in terms of non-intuitive ordering of const and volatile. Unless the syntax is memorable, it will not be used or used correctly. There is such a thing as being excessively parsimonius with keywords. One can take this discussion further. Imagine an object that contains a pointer to some data (e.g. an array class): class array { double *data; ... }; Now, we can distinguish (if we really want to) even more cases: 1) the array object is constant, and so is the data 2) the array object is constant, but the data could be changed 3).... n+1) this function promises not to change the data, but it might change the array object n+2) this function promises not to change the array object or the data m+1) this function promises not to even think about changing anything which might, in the future, be used to calculate future values which might be placed in the data of this array object. One can make this an arbitrarily complex situation by considering an item which can reside on a linked list: class ll { ll *next; ... }; Now, you can promise not to modify this object, or the one it points to, or the next one, but I might just modify the one after that. To encode things like that is silly: const volatile const const volatile const const const volatile ll(2);
davidm@uunet.UU.NET (David S. Masterson) (08/15/90)
In article <56514@microsoft.UUCP> jimad@microsoft.UUCP (Jim ADCOCK) writes:
A) volatile const. An object that appears const to the outside world that
may be changed internally. An example might be an object that caches
certain values for faster access in the future.
E) const volatile. Read-only permissions to an object that may be changing
of its own accord. A example might be a clock register to which write
permissions need to be denied, because people shouldn't be allowed to
change the time. Compilers can't optimize.
Might this not be more easily accomplished by public/private interfaces? In
the private interface of the object, the value is defined as volatile (or
'none'). However, the public interface would have a function that returns a
'const pointer*' to the private member data.
Also, 'volatile' and 'const' seem to differ on the key point of compiler
optimizations (the first can't be, but the second one can be). Doesn't the
(A) and (E) definitions hurt the 'const'ness of the object from the
compilation standpoint, so making the previous paragraph a more 'desirable
mode of operation?
I am one that believes 'const' should be constant and enforced as such by the
compiler. Casting is not a valid trick for getting around this.
--
====================================================================
David Masterson Consilium, Inc.
uunet!cimshop!davidm Mtn. View, CA 94043
====================================================================
"If someone thinks they know what I said, then I didn't say it!"
nelson@melodian.cs.uiuc.edu (Taed Nelson) (08/15/90)
In article <56514@microsoft.UUCP> jimad@microsoft.UUCP (Jim ADCOCK) writes: > > A) volatile const. An object that appears const to the outside world that > may be changed internally. An example might be an object that caches > certain values for faster access in the future. > > E) const volatile. Read-only permissions to an object that may be changing > of its own accord. A example might be a clock register to which write > permissions need to be denied, because people shouldn't be allowed to > change the time. Compilers can't optimize. Our project has often felt the need for a "mutable" object class, which would be essentially equivalent to the "volatile const" above. I also see the great need for the second. Personally, I do not think that a const --> non-const class should be allowed (or at least highly discouraged) for the same reason that you cannot cast from a register to a static storage. But that's all been discussed. The example where we really felt the need for something between non-const and const (and that being constant to the user but the object may change its internal state) is for objects which have a "current placeholder" type of field. For example, we have a Dictionary class which is kept current by the system, not the user / user program. Whenever the user wants to use the dictionary, we give them a constant reference to it. This allows them to extract entries (which return constant references as well) since that is a const member function. Sometimes, though, the user will want to iterate over the entire dictionary. For that reason, we provide them with various First() and Next() functions. Unfortunately, the "current entry" which is needed for the Next() needs to be changed, thus violating the "constness" of the object. We solved this problem by providing an "iterator" class which the user uses to provide the Next() functions (the iterator does the storing of the state information), but I don't think it very clean. Especially since it requires another class...
jimad@microsoft.UUCP (Jim ADCOCK) (08/16/90)
In article <1990Aug14.125734.28015@cbnewsm.att.com> gregk@cbnewsm.att.com (gregory.p.kochanski) writes: >AARGH! >One might as well use const(1) const(2) const(3) ... rather than >trying to express these things in terms of non-intuitive ordering >of const and volatile. Unless the syntax is memorable, it will not be >used or used correctly. There is such a thing as being excessively >parsimonius with keywords. Yes. You highlight well the concerns I had in proposing the ordering approach to overloading. What you don't do it make a counterproposal as to how to resolve the present problems with const. Any suggestions? Four different approaches might be: 1) Const means const. Period. No exceptions. 2) Volatile const and/or const volatile mean the same thing: A read-only object that can change of its accord. Const by itself means const without exception. Volatile const is then an object that cannot be enregistered in whole nor part. 3) Volatile const verses const volatile distinction. 4) Leave things the way they are now -- a muddle where people cast away from const, leading either to compilers that can't optimize on const, or compilers that do optimize on const, with "quasi-const" routines that break the caller's code.
shopiro@alice.UUCP (Jonathan Shopiro) (08/16/90)
The meaning of const has been discussed quite a bit around here. I used the term "bit-wise const" to mean that the storage may not be changed, and "meaning-wise const" otherwise. It is clear that both interpretations of const are meaningful and useful, and that neither subsumes the other. The higher-level abstractions that C++ was particularly designed to support require the meaning-wise interpretation of const, but effective use of read-only memory (which has traditionally been an important issue at AT&T) requires the bit-wise interpretation. Our compromise partitions the set of types. Meaning-wise const types are those with constructors or destructors or arrays of these. (Note that a type with a member or base that has a constructor or destructor automatically has one). The rest are bit-wise const. An implementation may not put a const instance of a meaning-wise const type into read-only memory, and a programmer may cast const aside from a pointer to a meaning-wise type and be assured that the program will work as if the pointer and its referent had never been declared const in the first place. E&S says only that const instances of bit-wise const types may be put into read-only memory, but I think it would be reasonable for X3J16 to extend this interpretation so that an implementation would be allowed to assume that a const pointer to a bitwise const type points to memory that will not change. (Const volatile would tell the implementation not to make this assumption). On a separate topic, I have heard there is some confusion about Sh[ao]piro. Jonathan Shapiro (shap@sgi.com) is a friend of mine and a C++ expert (so we usually agree), but he is not me. -- Jonathan Shopiro AT&T Bell Laboratories, Warren, NJ 07060-0908 research!shopiro (201) 580-4229
DEREK@AppleLink.apple.com (Derek White) (08/16/90)
In article <1990Aug14.224806.21375@brutus.cs.uiuc.edu> nelson@melodian.cs.uiuc.edu (Taed Nelson) writes: > The example where we really felt the need for something between non-const > and const (and that being constant to the user but the object may change > its internal state) is for objects which have a "current placeholder" type > of field. > > For example, we have a Dictionary class which is kept current by the system, > not the user / user program. Whenever the user wants to use the dictionary, > we give them a constant reference to it. This allows them to extract entries > (which return constant references as well) since that is a const > member function. > Sometimes, though, the user will want to iterate over the entire dictionary. > For that reason, we provide them with various First() and Next() functions. > Unfortunately, the "current entry" which is needed for the Next() needs to > be changed, thus violating the "constness" of the object. > > We solved this problem by providing an "iterator" class which the user > uses to provide the Next() functions (the iterator does the storing of the > state information), but I don't think it very clean. Especially since it > requires another class... I think the iterator solution is very clean. Seperate iterator classes allow multiple iterations over the same collection. They also neatly divide the job of iterating, and as you have shown, they pull out the variable part of a constant object. In fact, the recent examples that have shown a need for "partially const" objects have fallen in the same catagory: I think they can be solved by seperating the constant and variable portions of an object into constant and varying fields or classes. Although it may be expedient to make a statement that an object is const when it really isn't, I think in the long run honesty is the best policy. I suggest a hard line approach: const is constant. Derek White AppleLink: DEREK - Standard Discliamer -
bright@Data-IO.COM (Walter Bright) (08/16/90)
In article <56586@microsoft.UUCP> jimad@microsoft.UUCP (Jim ADCOCK) writes: <4) Leave things the way they are now -- a muddle where people cast away from <const, leading either to compilers that can't optimize on const, or compilers <that do optimize on const, with "quasi-const" routines that break the <caller's code. This is the best solution. There are cases where the user needs to cast away from const, even though he never changes the contents (such as in interfacing to an existing library which doesn't use pointers to const). As a case in point, let's try to implement an ANSI C memchr: void *memchr(const void *p,int c,size_t n) { while (n--) { if (*(const char *)p == c) return (void *) p; /* !!! */ p++; } return NULL; } (Note that, in ANSI C, you can use functions like memchr() to get rid of the 'constness' of a pointer even if you disallowed the cast! Therefore, disallowing the cast would require 2 versions of memchr(), one for const void* and the other for void*, yeech. The ripples of this problem go on and on...) Casting a void* to a const void* requires an explicit cast, and so the user is presumed to know what he's doing (just like in the rest of C). The optimizer can presume that the contents of non-volatile const variables do not change. If a user, by playing tricks, changes the variable, he loses. This is similar to the case: int a,b,*p; b = 4; p = &a; c = b * 3; p[1] = 5; /* changes b! */ d = b * 3; /* 12 is stored in d by an optimizing compiler, 15 by a non-optimizing one, though both compilers are ANSI conforming. */
shopiro@alice.UUCP (Jonathan Shopiro) (08/16/90)
In article <11194@alice.UUCP>, I wrote: > > > ... E&S says only that const instances of bit-wise const > types may be put into read-only memory, but I think it would be > reasonable for X3J16 to extend this interpretation so that an > implementation would be allowed to assume that a const pointer to a > bitwise const type points to memory that will not change. > Oops!! Consider the following perfectly reasonable function: void increase(int* ip, const int* cip) { *ip += *cip; } and a call to the function: main() { int i = 3; int* p = &i; const int* cp = &i; increase(p, cp); cout << *cp << "\n"; } This function had better print "6", but if my unthinking proposal were accepted, it would be permissible for it to print "3." Of course, in this case, the aliasing is obvious, but in general aliasing is impossible to detect at compile time. I would be willing to consider allowing the implementation to assume that memory pointed to by a const pointer to a bitwise const type points to memory that will not change, but only if the implementation can prove that the memory cannot be changed through any legally constructed alias. (For example the pointer points to an automatic variable in that function and the address of that variable has not been saved elsewhere). On a separate topic, I have changed my .signature but I am still the same person. -- Jonathan E. Shopiro AT&T Bell Laboratories, Warren, NJ 07059-0908 shopiro@research.att.com (201) 580-4229
gregk@cbnewsm.att.com (gregory.p.kochanski) (08/17/90)
In article <56586@microsoft.UUCP> jimad@microsoft.UUCP (Jim ADCOCK) writes: >What you don't do it make a counterproposal as to how to resolve the >present problems with const. Any suggestions? Four different approaches >might be: > >1) Const means const. Period. No exceptions. > 2) Volatile const, const volatile, and so forth >3) Volatile const verses const volatile distinction. >4) Leave things the way they are now -- a muddle where people cast away from My current C++ practice (also ANSI C) is to take option 1. I think the best way to handle this -- in an ideal world-- is to expand the concept of interface files so that they can report detailed information about the functions that they describe. This might allow objects to be partially const (use for cacheing information), and might allow deeper optimization (in cases where a const object contains a pointer where the pointed-to data might or might not be affected by the function). The question is "how?" Well, an off-the-cuff idea (no more than 10 min of thought) is the following: Extend the concept of function prototypes to also contain sortof pseudocode. The pseudocode would bethe minimum C++ necessary to describe what parts of an object change (or other interesting properties). Take a function int f(someclass& x), where class someclass { int sum_of_all_ps; public: int *p; int q; int sum() C {return sum_of_all_ps ? sum_of_all_ps : add_em_up(p);} }; Here, sum_of_all_ps is a cache which is presumably hard to calculate. So, if f is the following: int f(someclass& x) { x.q += x.sum(); return x.sum(); } We might have the following pseudocode: prototype int f(someclass& x) { x.q=0; } which would mean that f() can change x.q, but nothing else in x. Note that the need for 'const' as a keyword in function calls goes away entirely. Any comments? Greg Kochanski gpk@physics.att.com AT&T Physics Research -- Physics for fun and profit.
pcg@cs.aber.ac.uk (Piercarlo Grandi) (08/17/90)
On 15 Aug 90 21:28:31 GMT, DEREK@AppleLink.apple.com (Derek White) said: DEREK> In fact, the recent examples that have shown a need for DEREK> "partially const" objects have fallen in the same catagory: I DEREK> think they can be solved by seperating the constant and variable DEREK> portions of an object into constant and varying fields or DEREK> classes. Excellent point. Moreover 'const' in C++ already has the right definition; thank Stroustrup for not having botched it like in Ansi C. i would hate that any debate on 'const' opened the way for the usual call to greater Ansi C conformity. Fortunately C++ is quite a different language. DEREK> Although it may be expedient to make a statement that an object DEREK> is const when it really isn't, I think in the long run honesty is DEREK> the best policy. I suggest a hard line approach: const is DEREK> constant. Again, we go back to my usual and tired observation; what we wnat to achieve is reuse of interface, proof and implementation, and overloading keywords can only go so far to this end (see how strained is the syntax for the = 0 virtual function case). C++ should provide mechanisms towards this, and mostly implementation oriented. 'const' as such is a statement on the implementation of an object, not on its interface or semantics. -- Piercarlo "Peter" 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
chased@rbbb.Eng.Sun.COM (David Chase) (08/18/90)
Speaking not as a C++ expert (because I'm not) but as someone who might care about what an optimizing compiler could infer from "const", I think that #pragma's should be used to give the compiler hints to enable optimization, and that "const" (and its ilk) should be used in whatever sense is most helpful to programmers and program maintainers. The reasoning goes like this -- (1) there's LOTS of things that a compiler might want to know about the behavior of a subroutine, (2) and it isn't clear that "const" is the most important of them, (3) adding keywords or abusing combinations of existing keywords to get the additional information is likely to result in confusing cruft irrelevant to the correctness of the program (much like "register" in the old days). The choice of what is best to know about a program could well vary from processor to processor or from compiler to compiler, so it isn't clear that this should be standardized just yet. Thus, don't make it part of a language that is being standardized. For example, a procedure passed a pointer might modify it or not, or might store it in a global variable for later modification or examination. Pass the address of a local to a procedure that stores it in a global, and you can't eliminate tail-calls. Even if a procedure does assign through a pointer, it's good to know when it doesn't store the pointer someplace where OTHER procedures can assign through it. It's also slightly useful, on a multiprocessor, to know if a subroutine called from within a loop modifies global data. It's much easier to run the loop body in parallel if any subroutines called within the loop don't modify globally visible (shared) data. David Chase Sun Microsystems
davidm@uunet.UU.NET (David S. Masterson) (08/20/90)
In article <140877@sun.Eng.Sun.COM> chased@rbbb.Eng.Sun.COM (David Chase) writes: Speaking not as a C++ expert (because I'm not) but as someone who might care about what an optimizing compiler could infer from "const", I think that #pragma's should be used to give the compiler hints to enable optimization, and that "const" (and its ilk) should be used in whatever sense is most helpful to programmers and program maintainers. I agree with this sentiment, but think the thrust should be reversed (in general). That is, I think #pragma's should be used to give the compiler hints that standard optimization will not work with the current program. The language itself (since it derives from C) should promote optimization in its structure, not as a by-product of implementation specifics (as in the #pragma's). -- ==================================================================== David Masterson Consilium, Inc. uunet!cimshop!davidm Mtn. View, CA 94043 ==================================================================== "If someone thinks they know what I said, then I didn't say it!"
davidm@uunet.UU.NET (David S. Masterson) (08/20/90)
In article <PCG.90Aug17170737@athene.cs.aber.ac.uk> pcg@cs.aber.ac.uk (Piercarlo Grandi) writes: Again, we go back to my usual and tired observation; what we wnat to achieve is reuse of interface, proof and implementation, and overloading keywords can only go so far to this end (see how strained is the syntax for the = 0 virtual function case). C++ should provide mechanisms towards this, and mostly implementation oriented. 'const' as such is a statement on the implementation of an object, not on its interface or semantics. I agree that overloading keywords "can only go so far", but the interface of an object is *part* of the implementation of the object. I see no reason why 'const' is not also a statement of the interface to an object (as in constant, public pointer to private data areas). Thus far, I have not seen anything that needs a partial const that couldn't be solved by using a constant, public interface to a private data item. -- ==================================================================== David Masterson Consilium, Inc. uunet!cimshop!davidm Mtn. View, CA 94043 ==================================================================== "If someone thinks they know what I said, then I didn't say it!"
davidm@uunet.UU.NET (David S. Masterson) (08/20/90)
In article <2648@dataio.Data-IO.COM> bright@Data-IO.COM (Walter Bright) writes: As a case in point, let's try to implement an ANSI C memchr: void *memchr(const void *p,int c,size_t n) { while (n--) { if (*(const char *)p == c) return (void *) p; /* !!! */ p++; } return NULL; } Hmmm. I take it back, this does look like a problem with a constant 'const'. I'll have to think about this. -- ==================================================================== David Masterson Consilium, Inc. uunet!cimshop!davidm Mtn. View, CA 94043 ==================================================================== "If someone thinks they know what I said, then I didn't say it!"
jimad@microsoft.UUCP (Jim ADCOCK) (08/20/90)
In article <140877@sun.Eng.Sun.COM| chased@rbbb.Eng.Sun.COM (David Chase) writes: |Speaking not as a C++ expert (because I'm not) but as someone who |might care about what an optimizing compiler could infer from "const", |I think that #pragma's should be used to give the compiler hints to |enable optimization, and that "const" (and its ilk) should be used in |whatever sense is most helpful to programmers and program maintainers. | |The reasoning goes like this -- (1) there's LOTS of things that a |compiler might want to know about the behavior of a subroutine, (2) |and it isn't clear that "const" is the most important of them, (3) |adding keywords or abusing combinations of existing keywords to get |the additional information is likely to result in confusing cruft |irrelevant to the correctness of the program (much like "register" in |the old days). The choice of what is best to know about a program |could well vary from processor to processor or from compiler to |compiler, so it isn't clear that this should be standardized just yet. |Thus, don't make it part of a language that is being standardized. .... This is fine -- if C++ [as a whole] is willing to move away from the traditional "un*x" model of separate compilation, linking .h, .c files etc. -- Its just that lots of compromises have been made to the language already to make it fix into this traditional C-like model of compilation and linking. The problem I see it, if you give the compiler all this extra #pragma information, where does the compiler store the pragma information such that separately compiled modules can access it? -- I guess the #pragmas could go into the .h files, further weakening the distinction between interface and implementation. [And leading to even longer .h compilation times] Otherwise, one has to add new fields in the .o files, or add an additional database of information kept on a project-wide basis.... Optimizations based on const follows in a straight forward manner from const' ness -- if compilers can rely on const on a contractual basis, and require no information from a programmer beyond what programmer's currently are providing. Optimizations based on const also encourage good programming practice of declaring const on const things. ....Alternatively, maybe its just time to admit that the C-model of separate compilation/linking is done for, and toss out the C++ language hacks based on those restrictions. But please, lets not get into a worse of both worlds situation where the language hacks remain, but other decisions are made forcing C++ compilers towards auxiliary databases of information.
jimad@microsoft.UUCP (Jim ADCOCK) (08/21/90)
In article <2648@dataio.Data-IO.COM> bright@Data-IO.COM (Walter Bright) writes: |In article <56586@microsoft.UUCP> jimad@microsoft.UUCP (Jim ADCOCK) writes: |<4) Leave things the way they are now -- a muddle where people cast away from |<const, leading either to compilers that can't optimize on const, or compilers |<that do optimize on const, with "quasi-const" routines that break the |<caller's code. | |This is the best solution. There are cases where the user needs to cast |away from const, even though he never changes the contents (such as in |interfacing to an existing library which doesn't use pointers to const). | [memchr example] |(Note that, in ANSI C, you can use functions like memchr() to get rid |of the 'constness' of a pointer even if you disallowed the cast! |Therefore, disallowing the cast would require 2 versions of memchr(), one |for const void* and the other for void*, yeech. The ripples of this |problem go on and on...) Why? Why is this not exactly the solution people should be using -- Make a const and a non-const version of a routine, and thus avoid the const-hacking? Why allow the memchr code you suggest when, at best, its non-portable? Note: In an optimizing compiler its easy to imagine situations where the memchr example is going to bust caller code -- even if memchr *doesn't* change the value of the thing cast from const. Again, the problem is that calling a routine that violates its const'ness contract changes the optimization that can be performed in the caller's environment -- but in the face of separate compilation there's no way for the compiler to know that! |Casting a void* to a const void* requires an explicit cast, and so the user |is presumed to know what he's doing (just like in the rest of C). Even though what s/he's doing is potentially busting someone else's code that uses this separately compiled module? The problem is it's the calling sequence that gets busted when const'ness is violated -- not the called code! |The optimizer can presume that the contents of non-volatile const |variables do not change. If a user, by playing tricks, changes the variable, |he loses. This is similar to the case: | | int a,b,*p; | | b = 4; | p = &a; | c = b * 3; | p[1] = 5; /* changes b! */ | d = b * 3; /* 12 is stored in d by an optimizing compiler, | 15 by a non-optimizing one, though both | compilers are ANSI conforming. */ I don't think the examples are very similar. In the case given here, a programmer busts his/her own code. In the cast from const case, a programmer [potentially] busts someone else's code. And it might be a long time for that bug to show up in a definative manner. Chances are, programmer's would instead blame the compiler. And then compiler writers will pessimize, rather than optimize, on const. This to me is the bottom line. If compiler's cannot trust a function's const-ness on a contractual basis, compiler writer's will not create compilers that optimize on a function's const'ness. So again, in the particular case where a cast from const violates a functions implied contract, I think it deserves an error message, not a warning.
jimad@microsoft.UUCP (Jim ADCOCK) (08/21/90)
In article <11194@alice.UUCP> shopiro@alice.UUCP (Jonathan Shopiro) writes: | |Our compromise partitions the set of types. Meaning-wise const types |are those with constructors or destructors or arrays of these. (Note |that a type with a member or base that has a constructor or destructor |automatically has one). The rest are bit-wise const. An implementation |may not put a const instance of a meaning-wise const type into |read-only memory, and a programmer may cast const aside from a pointer |to a meaning-wise type and be assured that the program will work as if |the pointer and its referent had never been declared const in the |first place. E&S says only that const instances of bit-wise const |types may be put into read-only memory, but I think it would be |reasonable for X3J16 to extend this interpretation so that an |implementation would be allowed to assume that a const pointer to a |bitwise const type points to memory that will not change. (Const |volatile would tell the implementation not to make this assumption). Such an interpretation prevents the enregistering of members across a "const" member function. This puts C++ at a competitive disadvantage to other OOPLs that maintain function calls on a contractual basis. Eventually C++ would become know as "that slow OOPL", not "that fast OOPL." I think this would be a mistake. Const should mean const. Casts from const should be at best implementation defined when they violate a function's implied contract.
jimad@microsoft.UUCP (Jim ADCOCK) (08/21/90)
In article <CIMSHOP!DAVIDM.90Aug20002153@uunet.UU.NET> cimshop!davidm@uunet.UU.NET (David S. Masterson) writes: > >I agree with this sentiment, but think the thrust should be reversed (in >general). That is, I think #pragma's should be used to give the compiler >hints that standard optimization will not work with the current program. The >language itself (since it derives from C) should promote optimization in its >structure, not as a by-product of implementation specifics (as in the >#pragma's). I think this is a pretty good solution. Its only disadvantage is that the #pramga's need to be in the .h files, further eroding the distinct between specification and implementation. But, if you don't do it, you don't see the ugliness in your .h files.... This ties in well with the C++ philosophy that people who don't need a feature shouldn't be the ones to pay for it -- people who don't cast away from const shouldn't pay a price for those who do cast-away consts. I like this a lot better than Koenig's suggestion where everybody has to pay the price for cast-aways!
twagner@baobab.berkeley.edu (Tim Wagner) (08/27/90)
Unfortunately, what does one do when per-instance information is required? The following cannot possibly be acceptable to anyone: #pragma this-means-really-const const i; #pragma this-means-interface-const const j; ... 'pragma' as a replacement for language-integrated type information would be the supreme sacrifice in this case.