To const or to enum?

S

Steven T. Hatton

Any opinions or comments on the following? I don't say it below, but I came
out on the side of using enumerations over static constants.

/* I'm trying to figure out the pros and cons of using static const
* int class members as opposed to using enumerations to accomplish a
* similar goal. The use of static constants seems more explicit and
* obvious to me. Unless I assign values to the enumerators, the
* compiler will do it for me. This has the advantage of saving
* keystrokes and digital ink. It has the disadvantage that I have to
* calculate the actual numerical value if I want to know what it
* is. With static constants, it is right there poking you in the eye.
*
* Enumerations support a rudimentary form of type checking. It may be
* useful, but possibly also deceptive. Basically any value that fits
* in the storage location used by the compiler to hold the
* enumerators will convert to the enumeration type, even if it is not
* a value of any of the enumerators. That can be useful for bounding
* a range of values, but it may also lead to a false impression that
* you are using a 'type-safe' value.
*
* The type checking can keep me honest if I want to be. That is, if
* I use the type name as an indicator that a given parameter is
* intended to be of that enumeration type, I can use the enumerators
* by name without explicit type conversions of the form Type(val).
*
* An example of where public static integral constants are used is in
* the w3c DOM recommendations. They serve as a form of poor-man's
* type checking. Each node type is assigned an integral constant
* value with an associated name. The base type of all nodes holds the
* definitions of these constants, and each derived node is assigned
* a named constant identical to one of those named in the baseclass.
*
* A purist may argue that static_cast, or dynamic_cast should be used
* instead, but static_cast only works for types known at compile
* time, and dynamic_cast incurs overhead beyond that of using a
* statically bound integral constant. I may be incorrect regarding
* this last point, but I'm pretty sure of it.
*
*
*/

/*
http://xml.apache.org/xerces-c/ApacheDOMC++BindingL2.html
*/

/*
DOMNode.hpp:
class DOMNode
{
public:
enum NodeType {
ELEMENT_NODE = 1,
ATTRIBUTE_NODE = 2,
TEXT_NODE = 3,
CDATA_SECTION_NODE = 4,
ENTITY_REFERENCE_NODE = 5,
ENTITY_NODE = 6,
PROCESSING_INSTRUCTION_NODE = 7,
COMMENT_NODE = 8,
DOCUMENT_NODE = 9,
DOCUMENT_TYPE_NODE = 10,
DOCUMENT_FRAGMENT_NODE = 11,
NOTATION_NODE = 12,
};
*/

/*
* The following is code I created to test certan uses of const and
* enum. I'll be more than happy to see alternatives, or additional
* uses of these syntactic elements.
*/
#include <iostream>
class A {
public:

/*
* We can't use A::EA or A::x before they are declared.
* Therefore the constructor is placed after the declarations,
* which are, in the case, definitions.
*/
/*
A(const A::AE& ae=A::x )
{
if(ae > x)
{
std::cout << "ae > x \n";
}
}
*/

/* Concise, but ugly.*/
/*
static const int X=0,
Y=1,
Z=2;
*/

/* More verbose, but clearer */
static const int X=0;
static const int Y=1;
static const int Z=2;

/* More concise, but more obscure to the untrained eye.*/
enum AE {x,y,z};

/* Placed after declarations (definitions) so I can use them*/
A(const A::AE& ae=A::x )
{
if(ae > x)
{
std::cout << "ae > x \n";
}
}

void f(const AE& ae)
{
if(ae > x)
{
std::cout << "ae > x \n";
}
}

/* How to make a DOM to XML generator, or syntax checker.*/
void g(const AE& ae)
{
switch(ae)
{
case A::x: std::cout << "case A::x \n";
return;
case A::y: std::cout << "case A::y \n";
return;
case A::z: std::cout << "case A::z \n";
return;
}
}

void h(const AE& ae)
{
switch(ae)
{
case A::x: std::cout << "case A::x \n";
return;
case A::y: std::cout << "case A::y \n";
return;
case A::z: std::cout << "case A::z \n";
return;
}
}
};

void i(const A::AE& ae)
{
switch(ae)
{
case A::x: std::cout << "case A::x \n";
return;
case A::y: std::cout << "case A::y \n";
return;
case A::z: std::cout << "case A::z \n";
return;
}
}

int main()
{
A a;
a.f(A::x);
a.f(A::AE(A::Z));
a.f(A::AE(5));
}
 
D

David Hilsee

Steven T. Hatton said:
Any opinions or comments on the following? I don't say it below, but I came
out on the side of using enumerations over static constants.

/* I'm trying to figure out the pros and cons of using static const
* int class members as opposed to using enumerations to accomplish a
* similar goal. The use of static constants seems more explicit and
* obvious to me. Unless I assign values to the enumerators, the
* compiler will do it for me. This has the advantage of saving
* keystrokes and digital ink. It has the disadvantage that I have to
* calculate the actual numerical value if I want to know what it
* is. With static constants, it is right there poking you in the eye.

Either the value is important, or it's not. If it is important, then it
should be specified in the code. If it is not, then just let the compiler
do the work. Most of the enumerations that I have unimportant values that I
have never needed to know or calculate by hand.
* Enumerations support a rudimentary form of type checking. It may be
* useful, but possibly also deceptive. Basically any value that fits
* in the storage location used by the compiler to hold the
* enumerators will convert to the enumeration type, even if it is not
* a value of any of the enumerators. That can be useful for bounding
* a range of values, but it may also lead to a false impression that
* you are using a 'type-safe' value.

The entire C++ language presents a false impression of type-safety. Once UB
behavior occurs, all sorts of problems may occur. Sure, someone could
accidentally put an invalid value in the enum, but then again, someone could
also walk off the end of an array and cause all sorts of damage, so I won't
be losing any sleep over an enum. The "false impression" can be cured with
a little education.

* An example of where public static integral constants are used is in
* the w3c DOM recommendations. They serve as a form of poor-man's
* type checking. Each node type is assigned an integral constant
* value with an associated name. The base type of all nodes holds the
* definitions of these constants, and each derived node is assigned
* a named constant identical to one of those named in the baseclass.

The quoted header uses an enum, not static integers. Are you referring to
something else, or did you mistype something?
* A purist may argue that static_cast, or dynamic_cast should be used
* instead, but static_cast only works for types known at compile
* time, and dynamic_cast incurs overhead beyond that of using a
* statically bound integral constant. I may be incorrect regarding
* this last point, but I'm pretty sure of it.

I don't know of any purists that would argue for using static_cast with
enums, but that's a matter of taste. That should work just fine. However,
you can't use dynamic_cast because it's an enum, and not a polymorphic type.

/* More concise, but more obscure to the untrained eye.*/
enum AE {x,y,z};
<snip>

I think you mean "more concise, but more obscure to someone who doesn't know
the very basics of C or C++." :)
 
I

Ian

Steven said:
Any opinions or comments on the following? I don't say it below, but I came
out on the side of using enumerations over static constants.
Each has its place.
/* I'm trying to figure out the pros and cons of using static const
* int class members as opposed to using enumerations to accomplish a
* similar goal. The use of static constants seems more explicit and
* obvious to me. Unless I assign values to the enumerators, the
* compiler will do it for me. This has the advantage of saving
* keystrokes and digital ink. It has the disadvantage that I have to
* calculate the actual numerical value if I want to know what it
* is. With static constants, it is right there poking you in the eye.
*
How often do you have to know the numerical value? If you do, then
maybe an enum is not the correct option. In the DOM example you quote,
you will never (or at least I have never) have to know the numeric
value. An enum is what is says on the box, an enumeration - the value
don't matter.
* Enumerations support a rudimentary form of type checking. It may be
* useful, but possibly also deceptive. Basically any value that fits
* in the storage location used by the compiler to hold the
* enumerators will convert to the enumeration type, even if it is not
* a value of any of the enumerators. That can be useful for bounding
* a range of values, but it may also lead to a false impression that
* you are using a 'type-safe' value.
*
No, it won't. The compiler will barf on an attempt to pass an int to a
function with an enum parameter.
* The type checking can keep me honest if I want to be. That is, if
* I use the type name as an indicator that a given parameter is
* intended to be of that enumeration type, I can use the enumerators
* by name without explicit type conversions of the form Type(val).
*
? It's not an indicator, it's a requirement!
* An example of where public static integral constants are used is in
* the w3c DOM recommendations. They serve as a form of poor-man's
* type checking. Each node type is assigned an integral constant
* value with an associated name. The base type of all nodes holds the
* definitions of these constants, and each derived node is assigned
* a named constant identical to one of those named in the baseclass.
*
Don't forget these cam form IDL and Java bindings (there is (or at least
was) no C++ binding in the W3C specification). So the lowest common
denominator, const ints was used.
* A purist may argue that static_cast, or dynamic_cast should be used
* instead, but static_cast only works for types known at compile
* time, and dynamic_cast incurs overhead beyond that of using a
* statically bound integral constant. I may be incorrect regarding
* this last point, but I'm pretty sure of it.
*
When working with DOM, the dynamic_cast overhead tends to be trivial.
You have the choice of RTTI or the node type, again, remember this stuff
started as IDL, so it has to be language neutral.

Ian
 
D

David Hilsee

I don't know of any purists that would argue for using static_cast with
enums, but that's a matter of taste. That should work just fine. However,
you can't use dynamic_cast because it's an enum, and not a polymorphic
type.

After reading Ian's post, I realized that you were talking about
dynamic_cast being applied to the nodes in the DOM, not the enums
themselves. My mistake.

IMHO, the "purist" approach of using dynamic_cast doesn't buy you much. You
still wind up in a switch/case-like structure. I'm not even sure that
purists would normally argue for dynamic_cast. Most of the time, they argue
against it, opting for virtual functions instead. In this case, they might
argue for the visitor pattern, which may look better to some and confusing
to others.
 
S

Steven T. Hatton

David said:
The quoted header uses an enum, not static integers. Are you referring to
something else, or did you mistype something?

No. I was just speaking in abstract terms. Static binding means a binding
that occurs before execution. I should have been more careful to make the
distinction. Enumerators are not integral types as defined in ISO/IEC
14882:2003, nor are they declared static const. Nonetheless, from a
conceptual perspective they are virtually identical. I was actually
thinking in terms of the w3c IDE recommendation.

http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/idl-definitions.html
interface Node {

// NodeType
const unsigned short ELEMENT_NODE = 1;
const unsigned short ATTRIBUTE_NODE = 2;
const unsigned short TEXT_NODE = 3;
const unsigned short CDATA_SECTION_NODE = 4;
const unsigned short ENTITY_REFERENCE_NODE = 5;
const unsigned short ENTITY_NODE = 6;
const unsigned short PROCESSING_INSTRUCTION_NODE = 7;
const unsigned short COMMENT_NODE = 8;
const unsigned short DOCUMENT_NODE = 9;
const unsigned short DOCUMENT_TYPE_NODE = 10;
const unsigned short DOCUMENT_FRAGMENT_NODE = 11;
const unsigned short NOTATION_NODE = 12;

//...
}

There is no w3c DOM binding recommendation for C++. What I posted was
Apache's proposed DOM binding.
I don't know of any purists that would argue for using static_cast with
enums,

I intended it as an alternative to using enums. If you use dynamic_cast you
don't need the enums. See §12.2.5 of TC++PL(SE) for a discussion of the use
of type fields (enums or static constants in this discussion).
but that's a matter of taste. That should work just fine.
However, you can't use dynamic_cast because it's an enum, and not a
polymorphic type.

As I said, that incurs more overhead than the use of statically bound
constants.
<snip>

I think you mean "more concise, but more obscure to someone who doesn't
know the very basics of C or C++." :)

That is not what I meant. Understanding is one thing. familiarity another.
I've understood what an expression of that form means for over a decade.
But that doesn't mean that it would be as immediately recognizable as the
explicitly named static constants, or explicitly initialized enumerators.
The reason I use the w3c DOM as an example is because the numerical values
are explicitly specified. A programmer could not, for example, add an
enumerator to the beginning of the list above and remove the explicit
initializations without deviating from the interface specification.
 
S

Steven T. Hatton

David said:
"David Hilsee" <[email protected]> wrote in message
IMHO, the "purist" approach of using dynamic_cast doesn't buy you much.
You
still wind up in a switch/case-like structure. I'm not even sure that
purists would normally argue for dynamic_cast. Most of the time, they
argue
against it, opting for virtual functions instead.

Yes, but that only works if you are invoking a method on the object, not if
you are trying to determine what to do with the object as a parameter to a
function, etc.
In this case, they
might argue for the visitor pattern, which may look better to some and
confusing to others.

It depends on what you are trying to accomplish. One problem with building
all your smarts into the node and its derivatives is that it requires some
very convoluted inheritance to create any kind of recursive instantiation.
I have built a parser that consisted of nodes that thought for themselves.
But I had to feed it meaningful tokens one at a time.
 
D

David Hilsee

It depends on what you are trying to accomplish. One problem with building
all your smarts into the node and its derivatives is that it requires some
very convoluted inheritance to create any kind of recursive instantiation.
I have built a parser that consisted of nodes that thought for themselves.
But I had to feed it meaningful tokens one at a time.

I'm not sure I understand what you're saying. I was just pointing out that
the dynamic_cast could be eliminated with the visitor pattern, and that
might be supported by purists. I don't understand why that means the node
"thinks for itself" or why that means you have to feed it "one at a time."
For the visitor pattern, the node just has to override one member function
with a simple implementation (void accept(Visitor * p){p->visit(this);}).
What are you saying is the issue that results from this?

BTW, I'm not a huge fan of the visitor pattern. I was just pointing out
what some people might prefer.
 
D

David Hilsee

Steven T. Hatton said:
David Hilsee wrote:

That is not what I meant. Understanding is one thing. familiarity another.
I've understood what an expression of that form means for over a decade.
But that doesn't mean that it would be as immediately recognizable as the
explicitly named static constants, or explicitly initialized enumerators.
The reason I use the w3c DOM as an example is because the numerical values
are explicitly specified. A programmer could not, for example, add an
enumerator to the beginning of the list above and remove the explicit
initializations without deviating from the interface specification.

Well, I meant that as a joke to poke fun at those who might find enums
strange or mildly confusing, but I am surprised that you would say that
enums without explicitly specified values are not immediately recognizable.
I learned about enums when I first learned C, I have never had to think
twice about them since then. To me, it is immediately recognizable, easily
understandable, and very familiar, even though I do not often use enums or
see them used in practice. I guess I just found them very intuitive and
simple.
 
C

Conrad Weyns

Steven T. Hatton said:
Any opinions or comments on the following? I don't say it below, but I came
out on the side of using enumerations over static constants.

/* I'm trying to figure out the pros and cons of using static const
* int class members as opposed to using enumerations to accomplish a
* similar goal. The use of static constants seems more explicit and
* obvious to me. Unless I assign values to the enumerators, the
* compiler will do it for me. This has the advantage of saving
* keystrokes and digital ink. It has the disadvantage that I have to

[...]
I.m.o the problem with enums is that they get abused.
One should be able to change the position and values of an enum entry
at any time without breaking code.

An enum is indeed a type and has many advantages over const variables.
Enums look like classes but do not profit from the implicit namespace that a
class has, in other words:
EColType::Red
is illegal. This is unfortunate. Yet ms vc7 will eat accept!

The type of an enum member is not known and should never be assumed.

Here is what I try to adhere to when using enums:

1. Prefix every enum member with the enum name unless the enum
is part of a class in which case it "can" be left out:

enum EColorType
{
EColTypeInvalid,
EColTypeRed,
EColTypeBlue
};

This is necessary because enum members have global scope wether we
like it or not. In any sizeable project, good descriptive names get very
quickly used up.

2. Never ever cast an enum member.
Allways provide a complete (however large) switch to map enum
members to and from other context appropriate values.
This mapping is vital because this is the place where published
values (as read from and written to a data base) are maintained and
de-coupled from the enum.

3. Never ever use enum members in loop indexes and loop limits.

Regards,
Conrad Weyns
 
N

Niels Dekker - no reply address

Conrad said:
Here is what I try to adhere to when using enums:

1. Prefix every enum member with the enum name unless the enum
is part of a class in which case it "can" be left out:

enum EColorType
{
EColTypeInvalid,
EColTypeRed,
EColTypeBlue
};


Your prefix "EColType" differs from the enum name, but I guess that's a
typo...? Still, shouldn't we use a little namespace instead? Like
this:

namespace Color
{
enum Type
{
Invalid,
Red,
Blue
};
};


Regards,

Niels Dekker
www.xs4all.nl/~nd/dekkerware
 
C

Conrad Weyns

Niels Dekker - no reply address said:
Your prefix "EColType" differs from the enum name, but I guess that's a
typo...?

Indeed! It's a good job I am not anyone's compiler... :)

Still, shouldn't we use a little namespace instead? Like
this:

namespace Color
{
enum Type
{
Invalid,
Red,
Blue
};
};

Several years ago I did it this way. These days, if it makes sense to
"group" enums I use a class instead of a namespace.
Namespaces tend to leak into header files, even precompiled headers.
Particularly when they contain entities that are "global" in nature.
I find that the use of :: in code quickly becomes an eye catcher.
EColorRed is unambiguous compared to Color::Red
A matter of code convention in the end, no big deal realy :)
Regards,
Conrad Weyns
 
D

David Hilsee

2. Never ever cast an enum member.
Allways provide a complete (however large) switch to map enum
members to and from other context appropriate values.
This mapping is vital because this is the place where published
values (as read from and written to a data base) are maintained and
de-coupled from the enum.

3. Never ever use enum members in loop indexes and loop limits.

I'd feel free to break either of those two rules if the only place they are
broken is in the component that declares the enum. I've even written a
light wrapper around an enum to make it a little more user-friendly, and
inside of it, both of those rules were broken. I figure it's OK as long as
I'm not chasing down loops and casts in various unrelated modules. It may
be harder to take this approach if it will create dependency issues (e.g.
the database layer must have a mapping for an enum that it did not declare,
or another component needs to map to a DB-related enum), but it still might
be possible to work something out.
 
S

Steven T. Hatton

David said:
Well, I meant that as a joke to poke fun at those who might find enums
strange or mildly confusing, but I am surprised that you would say that
enums without explicitly specified values are not immediately
recognizable. I learned about enums when I first learned C, I have never
had to think
twice about them since then. To me, it is immediately recognizable,
easily understandable, and very familiar, even though I do not often use
enums or
see them used in practice. I guess I just found them very intuitive and
simple.

It depends on the situation. If it is simply a question of recognizing that
the symbold in the enum are enumerators, they there should be little
problem. If you are looking through code to find where thefollowing
definitions form the IDE are bound in C++, and what their values are, I
believe listing them as static const (or at least with explicit value
assignments for each enumerator) will be more immediately obvious than the
alternative shown below.

const unsigned short INDEX_SIZE_ERR = 1;
const unsigned short DOMSTRING_SIZE_ERR = 2;
const unsigned short HIERARCHY_REQUEST_ERR = 3;
const unsigned short WRONG_DOCUMENT_ERR = 4;
const unsigned short INVALID_CHARACTER_ERR = 5;
const unsigned short NO_DATA_ALLOWED_ERR = 6;
const unsigned short NO_MODIFICATION_ALLOWED_ERR = 7;
const unsigned short NOT_FOUND_ERR = 8;
const unsigned short NOT_SUPPORTED_ERR = 9;
const unsigned short INUSE_ATTRIBUTE_ERR = 10;
// Introduced in DOM Level 2:
const unsigned short INVALID_STATE_ERR = 11;
// Introduced in DOM Level 2:
const unsigned short SYNTAX_ERR = 12;
// Introduced in DOM Level 2:
const unsigned short INVALID_MODIFICATION_ERR = 13;
// Introduced in DOM Level 2:
const unsigned short NAMESPACE_ERR = 14;
// Introduced in DOM Level 2:
const unsigned short INVALID_ACCESS_ERR = 15;
// Introduced in DOM Level 3:
const unsigned short VALIDATION_ERR = 16;
// Introduced in DOM Level 3:
const unsigned short TYPE_MISMATCH_ERR = 17;

This form is far less immediately obvious than the IDL definition:
enum{
INDEX_SIZE_ERR = 1, DOMSTRING_SIZE_ERR, HIERARCHY_REQUEST_ERR,
WRONG_DOCUMENT_ERR,
INVALID_CHARACTER_ERR, NO_DATA_ALLOWED_ERR, NO_MODIFICATION_ALLOWED_ERR,
NOT_FOUND_ERR, NOT_SUPPORTED_ERR, INUSE_ATTRIBUTE_ERR, INVALID_STATE_ERR,
SYNTAX_ERR, INVALID_MODIFICATION_ERR, NAMESPACE_ERR, INVALID_ACCESS_ERR,
VALIDATION_ERR, TYPE_MISMATCH_ERR
}

Even this form is less expressive, though logically (practically) identical
to the IDL:
enum{
INDEX_SIZE_ERR = 1,
DOMSTRING_SIZE_ERR,
HIERARCHY_REQUEST_ERR,
WRONG_DOCUMENT_ERR,
INVALID_CHARACTER_ERR,
NO_DATA_ALLOWED_ERR,
NO_MODIFICATION_ALLOWED_ERR,
NOT_FOUND_ERR,
NOT_SUPPORTED_ERR,
INUSE_ATTRIBUTE_ERR,
INVALID_STATE_ERR,
SYNTAX_ERR,
INVALID_MODIFICATION_ERR,
NAMESPACE_ERR,
INVALID_ACCESS_ERR,
VALIDATION_ERR,
TYPE_MISMATCH_ERR
}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,989
Messages
2,570,207
Members
46,783
Latest member
RickeyDort

Latest Threads

Top