17

I often find myself writing helper debugger methods that return a printable string, given some enum value. The reason for this is when you typically log an enum, all you get is a number really. I hate having to go back to my source to figure out what that enum is. So I would do something like

typedef enum
{
   kOne = 1,
   kTwo,
   kThree,
}
MyEnum;

NSString *debugStringForEnum(MyEnum e)
{
    switch ( e )
        case kOne:
            return @"One";
        case kTwo:
            return @"Two";
        ....
}

....
NSLog(@"My debug log: %@", debugStringForEnum(someVariable));

So my question is, is there any way to avoid writing all this helper code, just to see the label value of enum?

Thanks

iDev
  • 23,310
  • 7
  • 60
  • 85
D.C.
  • 15,340
  • 19
  • 71
  • 102
  • As far as I know there isn't a way to do this, short of writing your own method, as you have done. – Nik Bougalis Mar 05 '13 at 22:11
  • Unfortunately it is not possible since enum names are not available past compilation. – iDev Mar 05 '13 at 22:22
  • possible duplicate of [Convert objective-c typedef to its string equivalent](http://stackoverflow.com/questions/1094984/convert-objective-c-typedef-to-its-string-equivalent). You can use one of the suggested methods there. – iDev Mar 05 '13 at 22:22
  • @ACB: Pretty much every suggestion in that link is the same crummy approach I'm already doing. – D.C. Mar 05 '13 at 22:27
  • @darren, Yes, that is because as I mentioned, it is not possible to access those enum names. So you have to use one of those ways. – iDev Mar 05 '13 at 22:28
  • FWIW, Visual studio shows the enum label in the debugger instead of the numeric value. – Inverse Mar 11 '13 at 17:29

4 Answers4

23

If you're willing to write "naughty" code that makes other developers cry, then yes. Try this:

#define ENUM(name, ...) typedef enum { M_FOR_EACH(ENUM_IDENTITY, __VA_ARGS__) } name; \
char * name ## _DEBUGSTRINGS [] = { M_FOR_EACH(ENUM_STRINGIZE, __VA_ARGS__) };

#define ENUM_IDENTITY(A) A,
#define ENUM_STRINGIZE(A) #A,

ENUM(MyEnum,
        foo, bar, baz, boo
        )

You obviously need a for-each macro to make this work. Here's a simple one:

#define M_NARGS(...) M_NARGS_(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define M_NARGS_(_10, _9, _8, _7, _6, _5, _4, _3, _2, _1, N, ...) N

#define M_CONC(A, B) M_CONC_(A, B)
#define M_CONC_(A, B) A##B

#define M_FOR_EACH(ACTN, ...) M_CONC(M_FOR_EACH_, M_NARGS(__VA_ARGS__)) (ACTN, __VA_ARGS__)

#define M_FOR_EACH_0(ACTN, E) E
#define M_FOR_EACH_1(ACTN, E) ACTN(E)
#define M_FOR_EACH_2(ACTN, E, ...) ACTN(E) M_FOR_EACH_1(ACTN, __VA_ARGS__)
#define M_FOR_EACH_3(ACTN, E, ...) ACTN(E) M_FOR_EACH_2(ACTN, __VA_ARGS__)
#define M_FOR_EACH_4(ACTN, E, ...) ACTN(E) M_FOR_EACH_3(ACTN, __VA_ARGS__)
#define M_FOR_EACH_5(ACTN, E, ...) ACTN(E) M_FOR_EACH_4(ACTN, __VA_ARGS__)
#define M_FOR_EACH_6(ACTN, E, ...) ACTN(E) M_FOR_EACH_5(ACTN, __VA_ARGS__)
#define M_FOR_EACH_7(ACTN, E, ...) ACTN(E) M_FOR_EACH_6(ACTN, __VA_ARGS__)
#define M_FOR_EACH_8(ACTN, E, ...) ACTN(E) M_FOR_EACH_7(ACTN, __VA_ARGS__)
#define M_FOR_EACH_9(ACTN, E, ...) ACTN(E) M_FOR_EACH_8(ACTN, __VA_ARGS__)
#define M_FOR_EACH_10(ACTN, E, ...) ACTN(E) M_FOR_EACH_9(ACTN, __VA_ARGS__)

It should be obvious how to extend that loop to have a longer upper limit, but... space considerations for this answer. The loop can potentially be as long as you're willing to copy-and-paste extra iterations into this bit.

For the non-debug build, have an #ifdef choose a version of ENUM without the second line.


EDIT: To steal the designated initialisers idea from teppic, here's an even more horrific version that also works with non-ordered initialiser values:

#define ENUM(name, ...) typedef enum { M_FOR_EACH(ENUM_ENAME, __VA_ARGS__) } name; \
char * name ## _DEBUGSTRINGS [] = { M_FOR_EACH(ENUM_ELEM, __VA_ARGS__) };

#define ENUM_ENAME(A) M_IF(M_2ITEMS(M_ID A), (M_FIRST A = M_SECOND A), (A)),
#define ENUM_ELEM(A) M_IF(M_2ITEMS(M_ID A), ([M_FIRST A] = M_STR(M_FIRST A)), ([A] = M_STR(A))),

#define M_STR(A) M_STR_(A)
#define M_STR_(A) #A
#define M_IF(P, T, E) M_CONC(M_IF_, P)(T, E)
#define M_IF_0(T, E) M_ID E
#define M_IF_1(T, E) M_ID T
#define M_2ITEMS(...) M_2I_(__VA_ARGS__, 1, 0)
#define M_2I_(_2, _1, N, ...) N
#define M_FIRST(A, ...) A
#define M_SECOND(A, B, ...) B
#define M_ID(...) __VA_ARGS__

ENUM(MyEnum,
        foo, bar, baz, boo
        )

ENUM(NotherEnum,
        A, B, (C, 12), D, (E, 8)
        )

I cannot guarantee your personal safety if you use this kind of thing in code someone else has to maintain.

Alex Celeste
  • 12,824
  • 10
  • 46
  • 89
  • 1
    Eww. you win +1 internets for that post though :) – Michael Dorgan Mar 05 '13 at 22:27
  • I wrote an example how to macro-ize the function parts here: http://stackoverflow.com/a/14931957/1162141 – technosaurus Mar 05 '13 at 22:36
  • 1
    Well if people will vote this up I'm tempted to submit an answer that involves inspecting the executable for debug information... – Ben Jackson Mar 06 '13 at 02:18
  • @darren: Well I did do some experiments. Fire up `gdb` and try `ptype MyEnum` as a proof of concept. If you are willing to add a compile-time step you could probably make `gdb` `info types` plus a `sed` script that autogenerates decodes for all enums. For some reason the Linux system I was testing this on wasn't showing `typedef` *names* although it worked on other systems. – Ben Jackson Mar 06 '13 at 08:20
9

A simpler method is to set up an array of string literals that replicate the labels according to their position in the array , e.g.

char *enumlabels[] = { NULL, "KOne", "KTwo", "KThree"};

Here you'd need to fill in the gaps to make the enum values match the array positions.

Or, better, for C99 with designated initialisers:

char *enumlabels[] = { [1] = "KOne", [2] = "KTwo", [3] = "KThree"};

In this case, as long as the enum declaration comes first, you can swap the array subscripts directly for the enum values to make it much clearer, e.g. { [kOne] = "kOne", ... }.

then for MyEnum e you could just use printf("%s\n", enumlabels[e]); or some such.

I've written a bit of code to demonstrate this much better:

typedef enum {
   white = 1,
   red   = 2,
   green = 4,
   blue  = 8,
   black = 16
} Colour;   // be sure to update array below!

char *enum_colours[] = { [white] = "white", [red] = "red", [green] = "green",
                         [blue] = "blue", [black] = "black" };

Colour c = blue;
printf("The label for %d is %s\n", c, enum_colours[c]);

Output: The label for 8 is blue

If you have huge enum constants (e.g. 32767) this is obviously not an ideal solution, due to the size of array required. Without designated initialisers, you could assign the array values directly, if more laboriously, with enum_colours[white] = "white"; and so on, but only in a function.

teppic
  • 8,039
  • 2
  • 24
  • 37
  • I like this solution except that I worry about what happens when someone adds another element to the enum but forgets to extend the description array... – Kendall Helmstetter Gelner Mar 05 '13 at 22:20
  • Place them in a struct together so that they both need to be updated together. – Michael Dorgan Mar 05 '13 at 22:25
  • 1
    This makes an assumption that enum value starts at zero which may not be the case always. It can be defined to start at any number. – iDev Mar 05 '13 at 22:25
  • Enum values do start at zero unless specified otherwise. If the enum values were spread around at random values, it'd be more bother. – teppic Mar 05 '13 at 22:28
2

You can't get to the enum names at run-time since those symbols are long gone by then, but you can use multi-character constants to create more meaningful values for your enums.

#import <Foundation/Foundation.h>

NSString* debugString(int en) {
    return [NSString stringWithFormat:@"%c%c%c%c",
            (en>>24) & 0xff,
            (en>>16) & 0xff,
            (en>>8) & 0xff,
            en & 0xff];
}

typedef enum {
    kOne = 'One.',
    kTwo = 'Two.',
    kThree = 'Thre',
} MyEnum;

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        NSLog(@"kOne = %@", debugString(kOne));
        NSLog(@"kTwo = %@", debugString(kTwo));
        NSLog(@"kThree = %@", debugString(kThree));
    }
    return 0;
}

will print

kOne = One.
kTwo = Two.
kThree = Thre

on the console.

To keep debugString from generating garbage, each enum must be exactly four characters long (on OSX anyway). This is extremely compiler and platform dependent. It's good for debugging, but not much else.

Of course this won't work if you need the enums to have specific values or values that relate to each other.

Ferruccio
  • 98,941
  • 38
  • 226
  • 299
  • That is a good idea, I don't know why you would have garbage data, unless you didn't have any member that was larger than 16 bits... – Grady Player Mar 06 '13 at 02:01
0

The names themselves wont be available, but it is usually enough to give them an explicit number:

enum{
   dog = 100,
   cat = 200,
   problem = dog+cat, //trailing comma legal in C and C++11
};
Grady Player
  • 14,399
  • 2
  • 48
  • 76