GetDunne Wiki

Notes from the desk of Shane Dunne, software development consultant

User Tools

Site Tools


enum_class_rather_than_typedef_enum

This is an old revision of the document!


C++ enum declaration vs. C "typedef enum"

The notion of enumeration type was added to the C language in the ANSI C standard, first published in 1989, after the C language had already been in widespread use for a decade. The variable declaration

enum { kCat, kDog, kCow, kHorse } animal;

declares an integer variable animal, which is supposed to have only four permissible values, identified by the symbolic constant names kCat, kDog, kCow, and kHorse. It's common to see enum used together with typedef, to define an “enum type” (especially in a .h header file) which can subsequently be used to declare any number of variables with the same set of permissible values, e.g.

typedef enum { kCat, kDog, kCow, kHorse } Animal;
...
Animal a1, a2, a3;

This looks very nice, and it's tempting to think that we have succeeded in defining a truly new data-type, but in reality this is all just syntactic sugar. The C compiler treats enum variables just like integers (the number of bits used is compiler-specific). The user-specified symbolic names are assigned actual integer values by the compiler in an ordered sequence, usually starting with 0, and are also treated just like integers. That is, the declarations above are entirely equivalent to

#define kCat    0
#define kDog    1
#define kCow    2
#define kHorse  3
 
int animal;
int a1, a2, a3;

The following code would therefore be quite legitimate, and we quickly see that our hopes of defining a truly new data-type are nothing but empty hopes after all:

int x = kDog;        /* assign a symbolic Animal value to an int                   */
int y = kDog + 99;   /* use a symbolic Animal value in an int-valued expression    */
animal = 100;        /* assign an arbitrary int value to a variable of type Animal */

This is why enum constant-names are commonly prefixed with a lowercase “k” just like other #defined constants—because that's basically what they are.

In programming for embedded systems (especially microcontrollers), enum declarations are often used as a short-form alternative to groups of #defines, in cases where we would like to use symbolic names for values, but retain the power to specify their exact binary representation. This is entirely valid, even when carried over to C++.

The original C++ specification introduced a slightly different enum declaration syntax for defining enum types without having to use the typedef keyword:

enum Animal { kCat, kDog, kCow, kHorse };

Unfortunately, this is semantically equivalent to the C typedef declaration above, so now we have two different syntaxes defining an enumerated type, neither of which offers any type-safety. This has been rectified in the C++11 standard, with the introduction of enum classes. In C++11 and later, we can write

enum class Animal { cat, dog, cow, horse };

This defines a new data-type Animal, and a group of permissible symbolic-constant values, which work in a type-safe fashion. The “k” prefix is no longer needed, because the class declaration puts these symbols into their own namespace, which we have to use as a prefix, e.g. Animal::cat, which has the nice result that we could use the name cat as a constant in any number of enum class types, with no chance of getting confused among them. The C++ compiler will enforce type-safety rules, making it illegal to try to assign, say, a plain integer value to a variable of Animal type.

In an early version of VanillaJuce, the file SynthEnvelopeGenerator.h included the declaration

typedef enum
{
    kIdle,
    kAttack,
    kDecay,
    kSustain,
    kRelease
} EG_Segment;

This has since been changed to

enum class EG_Segment
{
    idle,
    attack,
    decay,
    sustain,
    release
};

and all uses of the symbolic names idle, attack, etc. are now changed to e.g. EG_Segment::idle. Moreover, I realized that the EG_Segment data type and values are not even used anywhere outside the SynthEnvelopeGenerator class, so I was able to move the entire enum class declaration inside the SynthEnvelopeGenerator class declaration—into the private part, in fact.

I had also used a typedef enum declaration in SynthParameters.h, so I could use the declared type in a few different .cpp files:

tyepdef enum
{
    kSine,
    kTriangle,
    kSquare,
    kSawtooth
} SynthOscillatorWaveform;

When I converted this to the newer enum class form, however, lots of code broke, and I quickly realized that I had fallen back on my old embedded C habits of assuming the symbolic names kSine, etc. could be treated like integers. In SynthParameters.cpp, I had the following:

const char* WFname[] = { "Sine", "Triangle", "Square", "Sawtooth" };
 
static SynthOscillatorWaveform LookupWF(String wfname)
{
    int wfIndex = 0;
    for (int i = 0; i < 4; i++)
    {
        if (!strcmp(wfname.toUTF8(), WFname[i]))
        {
            wfIndex = i;
            break;
        }
    }
    return (SynthOscillatorWaveform)wfIndex;
}

I had also used constructions like String(WFname[osc1Waveform]) to convert the value of a SynthOscillatorWaveform variable (in this case osc1Waveform) to a juce::String. I cleaned this up by adding a couple of simple functions in SynthParameters.cpp, for serializing and deserializing SynthOscillatorWaveform values:

String Serialize_SynthOscillatorWaveform(SynthOscillatorWaveform wf)
{
    if (wf == SynthOscillatorWaveform::sine) return "Sine";
    if (wf == SynthOscillatorWaveform::triangle) return "Triangle";
    if (wf == SynthOscillatorWaveform::square) return "Square";
    if (wf == SynthOscillatorWaveform::sawtooth) return "Sawtooth";
 
    // Did we define a new waveform type and forget to add it here?
    jassertfalse;
    return "Sine";
}
 
SynthOscillatorWaveform Deserialize_SynthOscillatorWaveform(String wfString)
{
    if (wfString == "Sine") return SynthOscillatorWaveform::sine;
    if (wfString == "Triangle") return SynthOscillatorWaveform::triangle;
    if (wfString == "Square") return SynthOscillatorWaveform::square;
    if (wfString == "Sawtooth") return SynthOscillatorWaveform::sawtooth;
 
    // Did we get something unexpected in our input string?
    jassertfalse;
    return SynthOscillatorWaveform::sine;
}
enum_class_rather_than_typedef_enum.1504230116.txt.gz · Last modified: 2017/09/01 01:41 by shane