230 likes | 625 Views
Improving Enumeration Types [N1513=03-0096] by David E. Miller Review by Yuriy Solodkyy http://parasol.tamu.edu/~yuriys/ Problem description Summary of issues Inconsistent relationship between enumerations of separate types Limitation to integral types
E N D
Improving Enumeration Types[N1513=03-0096]by David E. Miller Review by Yuriy Solodkyy http://parasol.tamu.edu/~yuriys/
Problem description • Summary of issues • Inconsistent relationship between enumerations of separate types • Limitation to integral types • Inability to specify actual storage space and type to be used • Potentially conflicting names, unless separated by namespace use • Denormalized tables mapping enumeration values to printable names and vice versa
1. Inconsistent relationship between enumerations of separate types Although it is not permitted to assign from one enumeration type to another, it is permitted to compare such values, in that the values are silently converted to integral types. enum Color { ClrRed, ClrOrange, ClrYellow, ClrGreen, ClrBlue}; enum Alert { CndGreen, CndYellow, CndRed }; Color c = ClrYellow; Alert a = CndYellow; int i = 0; c = a; // not allowed under existing rules if (c >= a) … // allowed, but most probably incorrect a = i; // not allowed, needs cast: a = static_cast<Alert>(i); i = a; // allowed, but looses type information of the value if (c >= i) … // allowed, but error prone to future changes
Proposed solution Extending the "explicit" keyword to enumeration types can eliminate the implicit conversion without breaking any existing code. explicit enum Color { ClrRed, ClrOrange, ClrYellow, ClrGreen}; explicit enum Alert { CndGreen, CndYellow, CndRed }; Color c = ClrYellow; Alert a = CndYellow; int i = 0; c = a; // not allowed under existing rules if (c >= a) … // won’t be allowed anymore a = i; // not allowed, needs cast: a = static_cast<Alert>(i); i = a; // won’t be allowed anymore by definition of explicit If (c >= i) … // won’t be allowed anymore
Issues • How to force conversion to int?static_cast<int>(c)reinterpret_cast<int>(c) • Allows verification of exhaustiveness of cases in switch statements based on enum type [2] • Library Solution?
2. Limitation to integral types Although it is common to consider enumerations to be specific values, as listed in the declarations, they are also used to represent bit combinations and ranges, among other purposes. It is convenient to be able to specify floating point values as enumerations, particularly as values to be passed to functions, which by being declared to take floating enumeration parameters, can be protected at compilation time against invalid values. Proposed solution Extending the allowed constants for enum types can extend enumeration types without breaking any existing code. enum FloatEnum { LowVal = 1.23, MidVal = 4.56, HighVal = 7.89 };
Issues • Why only floating point types? Type can also be infered from string, bool and char literals • What should be the type of enum that mixes several types of literals?
3. Inability to specify actual storage space and type to be used • The need to be able to know, definitely, how much space will be used by an enumeration variable, particularly in a packed struct • The need to be able to specify how that enumeration will be treated when used as a number, e.g. as signed or unsigned. Currently both are compiler/platform specific
Proposed solution Borrowing from a C# approach, it is suggested there be an option to specify the underlying data type, which would also be the type to which implicit numeric conversions would be made. When utilized, this option would supersede the current size and type specifications. typedef enum { Ver1 = 1, Ver2 = 2 } VersionSChar: signed char, VersionInt: int ;
Issues • Proposed syntax introduces name clash in the global namespace: e.g. there is Ver1 of type VersionInt and of type VersionSChar. • Why not use C-like prefix syntax:signed short enum A {…} ? • Library solution?
Library only solution What we really need is a type build on top of enum that : • Behaves like an original enum • Does not provide implicit conversion to int • Allows to specify storage requirements
First approximation template <class E> struct enumeration { enumeration(E e) : m_e(e) {} operator E() const { return m_e; } private: E m_e; }; This will allow us to write: enum Season { Spring, Summer, Autumn, Winter}; enumeration<Season> es = Summer; // Initialization by enum value Season s = es; // Casting back to enumval es = s; // Assignment es = 2; // Not allowed as with original enums es = ClrYellow; // Not allowed as with original enums
First problems Comparison with a value from different enum is still valid, while it shouldn’t:if (es >= ClrYellow) … // valid but misleading Original enum might have had some operators defined on it: [1]Season& operator++(Season& s){ switch(s){ case Spring: s = Summer; break; case Summer: s = Autumn; break; case Autumn: s = Winter; break; case Winter: s = Spring; break; } return s;};Season s = Spring; enumeration<Season> es = Summer;++s; // valid++es; // fails to compile Original enum can be explicitly initialized by intSeason s = Season(1);enumeration<Season> es = enumeration<Season>(1); // fails
Revised solution template <class E> struct enumeration { enumeration(E e) : m_e(e) {} explicit enumeration(int n) : m_e(E(n)) {} operator E() const { return m_e; } enumeration& operator++() { ++m_e; return *this; } bool operator>=(E e) const { return m_e > e; } bool operator>=(const enumeration& e) const { return m_e > e.m_e; } private: template <class U> bool operator>=(U e) const; private: E m_e; };
Issues • Similar has to be done for all overloadable operators • We make certain assumptions about return types which might not be the case. This should be resolved in C++0x by decltype proposal. • Size is still compiler/platform dependent
Tackling size/type problem Whenever certain alignment and size constraints have to be met union is often used: template <class E, class I = int> union enumeration { enumeration(E e) : m_e(e) {} explicit enumeration(int n) : m_e(E(n)) {} operator E() const { return m_e; } bool operator>=(E e) const { return m_e > e; } bool operator>=(const enumeration& e) const { return m_e > e.m_e; } enumeration& operator++() { ++m_e; return *this; } private: template <class U> bool operator>=(U e) const; private: E m_e; I m_i; // used only to define lower size limit }; This only partially resolves our problem as it defines lower limit on size but not an upper one
Alternative template <class E, class I = E> struct enumeration { enumeration(E e) : m_i(I(e)) {} explicit enumeration(int n) : m_i(I(n)) {} operator E() const { return E(m_i); } bool operator>=(E e) const { return E(m_i) > e; } bool operator>=(const enumeration& e) const { return E(m_i) > E(e.m_i);} enumeration& operator++(){ E e = E(m_i); ++e; m_i = I(e); return *this;} private: template <class U> bool operator>=(U e) const; private: I m_i; }; And appropriate usage would be: enumeration<Season,unsigned char> euc = Summer; assert(sizeof(euc) == sizeof(unsigned char));
4. Potentially conflicting names In a single scope, declaring two enumerations with values that have the same name results in a conflict.enum Color { Yellow, Red };enum Alert { Yellow, Green }; Problem can be resolved without changes to the language by wrapping up enum into namespace:namespace BaseColors { enum Color { Blue, Green, Red }; }BaseColors::Color c = BaseColors::Blue;enumeration<BaseColors::Color,char> eb = BaseColors::Blue; Such solution though seems to be counterintuitive to the novices who expect enum name to serve as a scope for name resolution:Color::Yellow, Alert::Green
Proposed solution Allow the enum name to be used as a scope modifier to the enumeration constants.
5. Denormalized tables mapping enumeration values to printable names and vice versa It is common to need functions to convert back and forth between enumeration values and printable strings corresponding to the names. Proposed solution The language should specify the conversion functions’ names and algorithms, including what to do when input is invalid. To avoid name collisions, it is suggested that the enumeration type be used as a disambiguating scope.
Issues • Examples of programs that use such facility often should be provided • In which encoding should it be parsed and represented? • Should it be case sensitive or case insensitive? • Exhaustiveness of manually coded switch can be warned by compiler.
“Compile time” mapping Just as an example of what modern compilers can do in regard to the previous problem [3]:inline const char* enumname(Season s) { switch(s) { case Spring: return "Spring"; case Summer: return "Summer"; case Autumn: return "Autumn"; case Winter: return "Winter"; }} Example of the code that uses enumname:std::cout << enumname(Spring) << std::endl; and code that compiler generated:std::cout << enumname(Spring) << std::endl;00401771 push offset string "Spring" (410200h) 00401776 push offset std::cout (414EE8h) 0040177B call std::operator<<<std::char_traits<char> > (401380h)
References 1. B. Stroustrup. “The Design and Evolution of C++”. Addison Wesley, March 1994. ISBN 0-201-54330-3. 2. Scott Langham. “Proposal: explicit switch”Discussion in comp.std.c++http://groups.google.com/group/comp.std.c++/browse_thread/thread/dd571aa4f927990b/596f9f38db29b9a4?lnk=st&q=explicit+switch&rnum=1#596f9f38db29b9a4 3. “Idea for ANSI standard - compile time hashing for speed“. Discussion in comp.std.c++http://groups.google.com/group/comp.std.c++/browse_thread/thread/c7921e75078e75cf/43084a6ffe9cd521?lnk=st&q=compile+time+map&rnum=5#43084a6ffe9cd521