Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are there any compiler / preprocesser tricks to debug print an enum's name?

Tags:

c

debugging

clang

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

like image 972
D.C. Avatar asked Mar 05 '13 22:03

D.C.


1 Answers

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.

like image 103
Leushenko Avatar answered Oct 13 '22 03:10

Leushenko