steve@taumet.com (Stephen Clamage) (06/27/90)
In article <PCG.90Jun26213003@rupert.cs.aber.ac.uk> pcg@cs.aber.ac.uk (Piercarlo Grandi) writes: >Ahhh. Too bad :-). Stroustrup loves enums, indeed they have been made >local scope to a class definition as a special case because of that. Too >bad, because consts make, IMNHO, enums redundant in C/C++... Not at all redundant. The declarations enum foo { zero, one, two }; and const int zero = 0; const int one = 1; const int two = 2; have entirely different semantics, since the former defines a type, and the identifiers zero, one, two are of type foo, not type int. So if you have the declarations int myfunc(foo); int i; and try to call it with myfunc(i); it is an error, since the actual parameter is not automatically converted to type foo. Enums thus provide greater type safety where desired. >Well, I am not even sure that it is legal to have const data members, >whether static or not, and then where they can be initialized. Another >of the many cases where C++ has been designed with little bother for >orthogonality or consistency... Again I beg to differ. Const data members, static or not, are perfectly legal, and initialized the same way as any other const object. Look at <iostream.h> for examples of their use (but not initialization). I also do not see to what inconsistency you refer. Perhaps g++ does not follow the C++ ARM, but please do not confuse g++ (or any specific C++ implementation) with the definition of the C++ language. Also note that g++ is not C++, but is really another language with a lot in common with C++. Example: file mystruct.h: struct mystruct { const int i; // one copy in each object static const int j; // only one copy in whole program static int k; // only one copy in whole program mystruct(int a) : i(a) { } // only way to init i }; file mystruct.c: #include "mystruct.h" static const int mystruct::j = 1; // initialized here only static int mystruct::k = 2; // initialized here only file myfile.c #include "mystruct.h" // now we can refer to static members of mystruct int z = mystruct::j + mystruct::k; mystruct x(3); // instance of a mystruct -- Steve Clamage, TauMetric Corp, steve@taumet.com
wmmiller@cup.portal.com (William Michael Miller) (06/28/90)
I fully agree with nearly everything Steve Clamage says in his comments to Piercarlo Grandi (<PCG.90Jun26213003@rupert.cs.aber.ac.uk>). There is one point in his example that needs some correction, though: > file mystruct.c: > #include "mystruct.h" > static const int mystruct::j = 1; // initialized here only > static int mystruct::k = 2; // initialized here only Both of the "static" modifiers in the definitions are incorrect. If allowed, "static" in the definition would mean that the member had internal linkage, which contradicts the statement in the ARM, section 9.4, that "static members of a global class have external linkage." The correct rule for static members (data and functions) is to put the "static" modifier in the class declaration and omit it in the definition. (See a bit later on in that chapter for examples of correct definitions of static members.) ----------------------------------------------------------------------------- William M. Miller, Glockenspiel, Inc.; P. O. Box 366, Sudbury, MA 01776-0003
mat@mole-end.UUCP (Mark A Terribile) (06/28/90)
> Consider the following short piece of code. ... > class X { // error codes for system. > . . . > static int my_error_3; > . . . > }; > . . . > int X::my_error_1 = 1; > ... I am trying to wrap generic > constants in a class, from which they will be available to clients via > inheritance. ... The code ... does basically what I want, except [that] > the member functions of class Y can change the value of the statics in > class X. What I would like to do is have the members of class X be static > consts. ... That's just fine. Inside the class, write static const int my_error_3; > ... That way, there will only be one copy of the values around in the > system, and they cannot be altered. ... Weeeellll ... given pointer misfeasance they can be altered, and although there is only one copy of each, there may be many copies of the addressing operations that access them. (There is a good argument here for allowing the initializer for a static member const to be written in the class header.) > system, and they cannot be altered. Yet I don't see how to do this. ... In just about the way you are doing it, except that ... > ... Seems I can define them as static to get only a single copy, which has > the problem that the values can be changed, or I can declare them to be > consts, with an appropriate constructor, but then I have multiple copies of > the consts. ... ... you must put the initialization in a program file instead of a header file. You should (at least w/AT&T's 2.0) get a single copy of that variable, initialized in just one place, and visible as a member of the class scope, either by member selection or by qualification. -- (This man's opinions are his own.) From mole-end Mark Terribile
pcg@cs.aber.ac.uk (Piercarlo Grandi) (06/29/90)
In article <274@taumet.com> steve@taumet.com (Stephen Clamage) writes: In article <PCG.90Jun26213003@rupert.cs.aber.ac.uk> pcg@cs.aber.ac.uk (Piercarlo Grandi) writes: >Ahhh. Too bad :-). Stroustrup loves enums, indeed they have been made >local scope to a class definition as a special case because of that. Too >bad, because consts make, IMNHO, enums redundant in C/C++... Not at all redundant. The declarations [ ... explains that, unlike ANSI C, int values cannot be assigned to enums without casting ... ] Enums thus provide greater type safety where desired. A very small dose of it, and only thanks to yet another special case rule, and with significan disadvantage. It is otherwise stated very clearly that enums are integral types, and can be used in any context where another integral type *could* be used; the reverse is true as well; what is not possible is to use non enums where an enum *must* be used. But is that important? To me it is more important to be able to specify the length of an integral type, which cannot be done (why ever?) for an enum. Maybe redundant is too strong a word, but frankly the small added type security (forbidding conversions in just one direction) is a very small advantage, and the loss of length specificaiton is bad; what happens in practice is that one ends writing things like: enum { one, some, many; }; typedef char unsigned ordinal; ordinal thumbrule[CASES] = { one, one, some, one, many ... }; And bang goes the type security. What one can do is to use a struct with bitfields, but I see it as very unnatural. Also, using a class with const static members gives qualification to the type's values. class eyes { static const char unsigned none, one, two, argos; }; class ordinal { static const char unsigned one, some, many; }; /* static? */ const char unsigned ordinal::one = 1, ordinal::some = 3, ordinal::many = 999; /* static? */ const char unsigned eyes::none = 0, eyes::one = 1, eyes::two = 2, eyes:argos = 100; Admittedly however with 2.0 and 2.1 also enums are now class local: class eyes { enum { none = 0, one = 1, two = 2, argos = 100 }; }; class ordinal { enum { one = 1, some = 3, many = 999 }; }; But the two things are essentially equivalent, and the former requires less special casing. If you like this kind of type security, why not lobby to make typedef's introduce new types instead of synonyms for old types? Why special case enums, and in one direction only? >Well, I am not even sure that it is legal to have const data members, >whether static or not, and then where they can be initialized. Let me make it obvious: I know that as of now you can, as a special case, have non static const class data members. But the problem is that in practice this depends on the language release, compiler, compiler release, and the phase of the moon. Again I beg to differ. Const data members, static or not, are perfectly legal, and initialized the same way as any other const object. Ahem. Not entirely. Non static const members can only be initialized in the constructor, and then only in the special initialization list of the constructor, as you comment in your example. In other words, const non static members are only legal for classes with constructors. Nothing is said of const static members... For example, what is the linkage of a static const internal of external? const by default implies internal, but static members are by default external. Which prevails? Maybe an answer exists, but like so many other things in C++ it is far from obvious. The practical answer is not to rely on underspecified features unless you are prepared to stick to the one compiler, one release principle. Example: struct mystruct { [ ... ] static const int j; // only one copy in whole program static int k; // only one copy in whole program [ ... ] }; file mystruct.c: #include "mystruct.h" static const int mystruct::j = 1; // initialized here only static int mystruct::k = 2; // initialized here only [ ... ] Ahhh. This is similar to the example I have used myself. But then I ask again: why did you put 'static' in the two definitions? Is that legal? What is the effect on the linkage of the definitions? As I have pointed out, it can be shown that G++ 1.36.x allows but essentially ignores the static in the definition, and makes the rule that says static members are external prevail (but only for static const or uninitialized static; linkage becomes internal for static non const but initialized, and for static functions). This is all I am prepared to rely upon; I will not rely upon any other compiler, compiler release, language version to do the same thing. Frankly, this is big nuisance time. I'd like to specify the linkage of a "static" class member. Maybe having chosen static for "static" calss members has not been very wise, because it has preempted the other meaning. Oh, curse the overloading of symbols and reserved words in C++. -- Piercarlo "Peter" Grandi | ARPA: pcg%cs.aber.ac.uk@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
wmmiller@cup.portal.com (William Michael Miller) (07/01/90)
pcg@cs.aber.ac.uk (Piercarlo Grandi) writes: > what is not possible is to use non enums where an enum *must* be > used. But is that important? Yes, it's quite important. An enumeration defines certain values as being the only ones allowed. Requiring an explicit cast to convert a non-enum value to an enum type is very valuable in insuring that you don't use a value that's outside the set of allowable ones. The presence of casts means you cannot be as type-secure in C++ as you can in Pascal, for instance, but making enums distinct types and forbidding automatic conversions to them is a major help in making things as secure as possible. The fact that automatic conversion *from* enum types is defined does not seem to me to be a major drawback, as the type to which you are converting is less constrained than the enum -- promotion is an integral part (excuse the pun) of C, and it's pretty reasonable. > the loss of length specificaiton is bad; what > happens in practice is that one ends writing things like: > > enum { one, some, many; }; > typedef char unsigned ordinal; > ordinal thumbrule[CASES] = { one, one, some, one, many ... }; I don't understand -- perhaps you've omitted the context which requires writing things this way, but it would seem much more natural to write enum ordinal { one, some, many }; ordinal thumbrule[CASES] = { one, one, some, one, many ... }; > But the two things are essentially equivalent, and the former requires > less special casing. They (static const members and nested enum types) are *not* equivalent: the members take up storage and are runtime values, requiring addressing and subject to being accidentally overwritten, while the enum values are compile-time quantities that can be used in, for example, immediate instructions and impossible on most computers to overwrite. We've already discussed the type issues distinguishing the two. I didn't understand the comment about "less special casing;" they do require *more* typing (keyboarding), however. > If you like this kind of type security, why not lobby to make typedef's > introduce new types instead of synonyms for old types? There is a place for both synonym type definition and derived type definition; it's a matter of debate which is more useful, but what is not open to debate is that synonym type definition is what we've inherited from C. The flexibility of C++'s class definitions allows them to work nearly as well as subtyping. ------------------------------------------------------------------------------ William M. Miller, Glockenspiel, Inc.; P. O. Box 366, Sudbury, MA 01776-0003 wmmiller@cup.portal.com BIX: wmiller CI$: 72105,1744
pcg@cs.aber.ac.uk (Piercarlo Grandi) (07/03/90)
In article <31284@cup.portal.com> wmmiller@cup.portal.com (William Michael Miller) writes: pcg@cs.aber.ac.uk (Piercarlo Grandi) writes: > what is not possible is to use non enums where an enum *must* be > used. But is that important? Yes, it's quite important. An enumeration defines certain values as being the only ones allowed. Requiring an explicit cast to convert a non-enum value to an enum type is very valuable in insuring that you don't use a value that's outside the set of allowable ones. Also subscript checking is incredibly valuable, and yet! This is C/C++... > If you like this kind of type security, why not lobby to make typedef's > introduce new types instead of synonyms for old types? There is a place for both synonym type definition and derived type definition; it's a matter of debate which is more useful, but what is not open to debate is that synonym type definition is what we've inherited from C. But the matter of enums has been open to debate... A foolish hobgoblin is the mind of little consistencies :-). > But the two things are essentially equivalent, and the former requires > less special casing. They (static const members and nested enum types) are *not* equivalent: the members take up storage and are runtime values, requiring addressing and subject to being accidentally overwritten, This is *only* because of a language misdesign problem; using 'static' to mean 'class object member' preempts its use to specify linkage, and thus the rule that static members always have external linkage. An alternative could have been that the linkage of class members of any type is specified in its *definition*, not in its declaration in the class definition. This is what GNU C++ implements, albeit only partially (e.g. only for function members). If we could have class object (instead of instance) const members with internal linkage, they would be "bodiless" consts like every other. > the loss of length specificaiton is bad; what > happens in practice is that one ends writing things like: > > enum { one, some, many; }; > typedef char unsigned ordinal; > ordinal thumbrule[CASES] = { one, one, some, one, many ... }; I don't understand -- perhaps you've omitted the context which requires writing things this way, but it would seem much more natural to write enum ordinal { one, some, many }; ordinal thumbrule[CASES] = { one, one, some, one, many ... }; The crucial point is that in my example an ordinal has sizeof (char unsigned), and in yours we do not know -- the implementation may or may not pack it (a way to control the size of an enum entity is to use bitfields in a struct, but here we have an array). C/C++ tend to be languages in which you want to control the storage you allocate to things, more than one in which you want type security -- it is *not* Pascal. So we have signed and unsigned whose sizes we can control, and enums where we cannot. We could say that enums, being integral types, should be subject to the same shortening and lengthing attributes (char, short, long) as signed and unsigned (well, I know that regrettably char has become more of a type in C++ 2.0 :-/), but again maybe it would be simpler to avoid special casing enums, and just change the rule about typedefs, so that new integral types could be introduced that way (which would have other positive consequences, like allowing operators on the new types, without requiring embedding in a class), etc... The flexibility of C++'s class definitions allows them to work nearly as well as subtyping. Precisely... In summary: the current C++ definition can be explained and maybe even justified, but certainly not excused in many areas, like the difficulties with members of the class object (which class object? :->) caused by overloading the keyword static. And departing from C in one area (enums) and not another (typedef). And... -- Piercarlo "Peter" Grandi | ARPA: pcg%cs.aber.ac.uk@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