Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
262 views
in Technique[技术] by (71.8m points)

c++ - Using enum in loops and value consistency

I'm a big fan of C++'s strong-typing features and what I like the most is to use enumerations while dealing with limited sets of data.

But enumerations lack some useful features, for example operators:

enum class Hex : int
{
    n00, n01, n02, n03,
    n04, n05, n06, n07,
    n08, n09, n10, n11,
    n12, n13, n14, n15
};

for (Hex h = Hex::n0; h <= Hex::n15; ++h) // Oops! no 'operator ++'
{ /* ... */ }

Is easy to get rid of the lack of operators creating a free operator on the same scope:

Hex &operator ++(Hex &h)
{
    int r = static_cast<int>(Hex);
    h = static_cast<Hex>(r + 1);
    return h;
}

for (Hex h = Hex::n0; h <= Hex::n15; ++h) // Now the '++h' works!
{
    std::cout << std::dec << int(h) << ": "
              << std::hex << int(h) << '
';
}

But this approach is more a nuisance than a solution, because it can break the value limitation of the enumeration: applying ++h while h equals to Hex::n15 will set h to he value 16, wich is out of the Hex scope of values while h is still of the type Hex!, This problem is more evident in other enumerations:

enum class Prime : int
{
    n00 = 2,   n01 = 3,   n02 = 5,   n03 = 7,
    n04 = 11,  n05 = 13,  n06 = 17,  n07 = 19,
    n08 = 23,  n09 = 29,  n10 = 31,  n11 = 37,
    n12 = 41,  n13 = 43,  n14 = 47,  n15 = 53
};

Prime &operator ++(Prime &p)
{
    // How to implement this?! will I need a lookup table?
    return p;
}

This problem was a surprise for me; I was betting that storing an incorrect value into an enumeration value will throw an exception. So, for now I was wondering if there's an elegant way to deal with this enumeration's weaknesses, the goals I want to achieve are:

  • Find a comfortable way to use enumeration values in loops.
  • Ensuring enumation data consistency between operations.

Additional questions:

  • Is there a reason for not throwing an exception when an enumeration data gets a value that is out of its possible values?
  • There is a way to deduce the type associated with an enumeration class?, the int type in the enumerations Hex and Prime.
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

As you've noticed, enum in C++ is not an enumerated type, but something more complex (or more mixed). When you define an enum, you define in fact two things:

  1. An integral type with a legal range sufficient to contain an or of all of the enumerated values. (Technically: the range is 2^n - 1, where n is the number of bits necessary to hold the largest value.)

  2. A series of named constants having the newly defined type.

(I'm not sure what happens with regards to the range if you explicitly specify an underlying type.)

Given your enum Prime, for example, the legal values would be all integers in the range [0...64), even if all of these values don't have a name. (At least if you didn't specifically say that it should be an int.)

It's possible to implement an iterator for enums without initializers; I have a program which generates the necessary code. But it works by maintaining the value in an integral type which is large enough to contain the maximum value plus one. My machine generated implementations of ++ on such an enum will assert if you try to increment beyond the end. (Note that your first example would require iterating h one beyond the last value: my implementation of the various operators does not allow this, which is why I use an iterator.)

As to why C++ supports the extended range: enum are often used to define bit masks:

enum X
{
    a = 0x01,
    b = 0x02,
    c = 0x04,
    all = a | b | c,
    none = 0
};

X operator|( X lhs, X rhs )
{
    return X((int)lhs | (int)rhs);
}
//  similarly, &, |= and &=, and maybe ~

One could argue that this use would be better handled by a class, but the use of enum for it is ubiquitous.

(FWIW: my code generator will not generate the ++, -- and the iterators if any of the enum values has an explicitly defined value, and will not generate |, & etc. unless all of the values have explicitly defined values.)

As to why there is no error when you convert some value outside the legal range (e.g. 100, for X, above) is simply in keeping with the general philosophy inherited from C: it's better to be fast than to be correct. Doing extra range checking would entail additional runtime cost.

Finally with regards to your last example: I don't see this as a realistic use of enum. The correct solution here is an int[]. While the C++ enum is rather a mixed breed, I would only use it as a real enumerated type, or for bit masks (and only for bit masks because it is such a widely established idiom).


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...