[comp.lang.c++] Static consts within classes?

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