fish.cx


Improve your life - install the IFL protocol today! (download)
Up to: fish.cx | Programming

enums

Enums are poor wee beasties with some rather different behaviour from the other types of classes. Still, isn't it nice the way they have no ascenders or descenders? Like (ifl:)overnumerousnesses, but shorter and usually better kerned.

Before we go any further, I should mention that this is partly a scratchpad for ideas, and I am far from a real expert on these things. Mistakes are likely, and indeed, have already happened. These are also quite C++ specific -- C enums differ in several important ways, see http://david.tribble.com/text/cdiffs.htm#C99-enum-const for details.


The enum hack

MSVC doesn't support inline definitions of static constants, so it's common to replace this:
    class MyClass
    {
        static const int some_constant = 5;
    };
with
    class MyClass
    {
        enum { some_constant = 5 };
    };
There's no similar solution for non-integer constants.

Enum entries change their type during declaration

Little known and almost entirely pointless fact - the type of an enum entry changes depending on where it's used. Before the closing "}" each entry has the same type as whatever is assigned to it, but after the "}", the enum is created as a new type, which each of the entries within it change to. The most likely place this might be an issue would be template code, and even that isn't very likely.

Enums can't be forward declared

quick U-turn... hope no-one saw that.
Just because enums are classes too, doesn't mean "enum MyEnum;" is legitimate without first declaring MyEnum in full. This page is full of examples where being a class doesn't count for anything. You can't inherit from an enum either (there are special case rules for that, viz. it has to be a struct or a class, not even union will cut it). That'll teach me to trust MSVC.

I don't have the standard, but apparently this has been discussed on comp.lang.c++.moderated: forward declaration of enums post to egcs-bugs in 1998. (ifl:)Mumit seems pretty sure too.

Matthew Wilson (the only C++ guru worth trusting for remotely practical advice) describes a technique to use excessive C++ trickery based on forward-declaring a struct with its cast-to-enum operator defined in his article Flexible C++ #12: Imperfect enums, Part 2: Forward Declarations (needs login, alas). It's clever, but probably too clever for its own good, given that the alternative is to say sod it and use an int, which everyone can understand.

Are empty enums allowed?

Yes, empty enums (i.e. "enum {};") are allowed.

What's the maximum value an enum can hold?

enums are always big enough to hold the maximum value declared within them. Any bigger than that isn't guaranteed. What is guaranteed is that if there aren't any values bigger than an int, the enum won't be bigger than an int. enums are always large enough to hold zero, even if they have no entries.

Commas at the end of enums

They may look pretty, they'll compile silently in most compilers, but they're not legal. Ask g++ --pedantic if you don't believe me. (They are legal in C99 though, apparently, but that lets you get away with murder.)

Enums don't take a ::

Enums introduce their identifiers outside of their own scope, and they don't have any methods, data, types or typedefs either, so there's nothing valid to write on the right of "MyEnum::". See the next heading.

Don't nest your enums in your classes

(This is rule-of-thumb, your-mileage-may-vary stuff)

Let's presume you have a class C with a private or protected enum, E. Unless E is being used for the enum hack, you will probably find that you later want to pass data of type E to another class, or use the constants that E defines in another class. The latter can be fixed with a friend declaration, but not without exposing all of the rest of C's innards too. If you've got your headers nice and modular, code that knows about C can usually stand to see E's constant names anyway. (If not, see the next item.) Of course, if you're pretty sure no other class will need to see it, then go ahead and put it in the class. Just don't say I didn't tell warn you.

boost's(maybe) smart_enum

A smart_enum<> template class has been submitted to boost. If that link dies, hopefully due to it being accepted into boost, you might want to try ifl:boost+smart_enum.

Nesting enums in wrapper structs or namespaces

Enums introduce names outside the scope of the surrounding {}. The only other places I can think of that happening in C++ are inline friends and Koenig lookup (the latter may well include the former). This means that it's easy to let them pollute the namespace. So one option is to wrap them in a struct and refer to that explicitly when using the identifiers.

class MyClass
{
public:
    struct EDirection { enum type { FWD, BWD }; };

    static
    bool is_forward( EDirection::type direction )
    {
        return direction == EDirection::FWD;
    }
};

The first problem with this is that you have to always include the "::type". The alternative requires you to implement all the int-like behaviour for the struct (construction, assignment, comparison operators, etc.). A templated version of this pattern might be helpful, but see the second problem before you rush off and implement it.

The second problem is that you can't drop the use of the prefix in certain limited scopes where you know what you're doing. This is the behaviour of namespaces, so wrapping the enum declaration in its own namespace is often better.

namespace NSDirection
{
    enum type { FWD, BWD };
}

class MyClass
{
public:
    //using namespace NSDirection; // not valid here, you have to do:
    typedef NSDirection::type EDirection;

    static
    bool is_forward( EDirection dir )
    {
        using namespace NSDirection;

        // ... the enum values FWD and BWD are now in scope ...

        return (dir == FWD);
    }

    static
    bool is_backward( EDirection dir );

    // ...or just to prove you don't need the using directive
    //    if you don't want it:
    static
    bool is_valid( EDirection dir )
    {
        return (dir == NSDirection::FWD)
            || (dir == NSDirection::BWD);
    }
};

// ...

// -- Implementation of MyClass --

using namespace NSDirection;
    // ... the enum values FWD and BWD are now in scope ...

// static
bool MyClass::is_backward( MyClass::EDirection direction )
    // The 'MyClass::' before EDirection is probably not
    // needed according to ISO C++, but it is for MSVC 6.0.
{
    return direction == BWD;
}

Incidentally, namespaces and typedefs are in different err.. namespaces. So you could call them both Direction. This would work fine except for the definition of is_valid, where Direction::FWD would confuse the compiler. Even sticking "namespace" in front of it wouldn't be allowed, so as far as I can tell, there would be no way to disambiguate. This is one of those nasty cases where everything seems to be working and breaks later when you implement something on top of it.

The downside is that there are lots of places where you can't declare a new namespace (since "a namespace definition must appear either at file scope or immediately within another namespace definition", that's almost anywhere). So you should either use the nested struct trick or always declare enums in namespaces and include the namespaces when you want them limited to use inside a class. I prefer namespaces, since they don't involve any extra work, and make it easy to share the type with other code. For methods whose implementation you put in a separate object file, you can just write "using namespace namespace-name;" at the top of the implementation file instead of inlined in each function.

Aside: Why you should always have declarations and definitions in the same namespace

If we wanted to keep the declaration of method MyClass::is_backward separated out, but still possible for most compilers to inline, one common technique is to have a myclass.inl file which we #include at the end of myclass.hpp. This file contains the definitions of all methods which we want to make available for inlining. If we were also trying to use MSVC or other scope-broken compilers (are there any others?), the method parameters would need to be explicitly specified for each of the definitions in myclass.inl.

The correct solution to this problem is to either a) use a better compiler or b) live with it, but let's presume that we don't know that yet...

One workaround which we might try would be to put "using namespace NSDirection;" or "typedef NSDirection::type EDirection;" at the top of myfile.inl, but this would remain in scope in any file that included myfile.hpp. The natural solution to this is to add scope limitation, in the form of a namespace (which could be anonymous) which wraps all of the code in myclass.inl. This would be a bad idea.

C++ doesn't have a way of referring to the enclosing namespace, so to do the definition of MyClass::is_backward(), we'd have to call it "::MyClass::is_backward()". This would compile, but we'd have inadvertently broken the modularity of the code. If in the future we were trying to make our code co-exist happily with another library, we might try to include myclass.hpp within a namespace. This would fail, due to ::MyClass no longer existing.

...On the other hand, you may feel that you can't sensibly expect inclusion of headers within namespaces to work anyway. You might be right. (example, anyone?)

Update:
Apparently, the Standard actually says this:

The unary '::' restricts name lookup to those names declared at global scope, or visible in global scope due to a using-declaration. (WP 3.4.2)

That seems to imply that in fact this may not be a problem after all, though I haven't thought about/tested the full ramifications yet. For actually good namespace advice, see this page, which I haven't read through properly yet myself: C++ namespaces

Fixed storage-allocation enums

See http://www.mvps.org/vcfaq/lang/3.htm for a simple implementation of fixed storage enums. It could be extended with the use of boost's integer traits to do some better checking. Anyone who implements this, please let me know.