Существуют ли какие-либо трюки компилятора/препроцессора для отладки печати имени перечисления?
Я часто нахожу, что пишу методы вспомогательного отладчика, которые возвращают печатаемую строку, учитывая некоторое значение перечисления. Причина этого в том, что, когда вы обычно регистрируете перечисление, все, что вы получаете, действительно число. Мне не нравится возвращаться к моему источнику, чтобы понять, что это за переименование. Поэтому я бы сделал что-то вроде
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));
Итак, мой вопрос: есть ли способ избежать написания всего этого вспомогательного кода, просто чтобы увидеть значение метки для перечисления?
Спасибо
Ответы
Ответ 1
Если вы готовы написать "непослушный" код, который заставляет других разработчиков плакать, тогда да. Попробуйте следующее:
#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
)
Вам явно нужен макрос для каждого из них. Здесь простой:
#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__)
Должно быть очевидно, как расширить этот цикл, чтобы иметь более длинный верхний предел, но... косвенные соображения для этого ответа. Цикл может быть потенциально до тех пор, пока вы готовы копировать и вставлять лишние итерации в этот бит.
Для не-отладочной сборки, #ifdef выберите версию ENUM без второй строки.
EDIT: Чтобы украсть выделенную идею инициализаторов из teppic, вот еще более ужасающая версия, которая также работает с неупорядоченными значениями инициализации:
#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)
)
Я не могу гарантировать вашу личную безопасность, если вы используете такую вещь в коде, которую кто-то должен поддерживать.
Ответ 2
Более простой способ - создать массив строковых литералов, которые реплицируют метки в соответствии с их положением в массиве, например.
char *enumlabels[] = { NULL, "KOne", "KTwo", "KThree"};
Здесь вам нужно заполнить пробелы, чтобы значения перечисления соответствовали позициям массива.
Или, лучше, для C99 с назначенными инициализаторами:
char *enumlabels[] = { [1] = "KOne", [2] = "KTwo", [3] = "KThree"};
В этом случае, до тех пор, пока первое объявление перечисления, вы можете поменять индексы массива непосредственно на значения перечисления, чтобы сделать его более понятным, например. { [kOne] = "kOne", ... }
.
то для MyEnum e
вы можете просто использовать printf("%s\n", enumlabels[e]);
или некоторые из них.
Я написал немного кода, чтобы продемонстрировать это намного лучше:
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
Если у вас есть огромные константы перечисления (например, 32767), это, очевидно, не идеальное решение из-за необходимого размера массива. Без назначенных инициализаторов вы можете присваивать значения массива напрямую, если более трудоемко, с помощью enum_colours[white] = "white";
и т.д., Но только в функции.
Ответ 3
Вы не можете получить имена перечислений во время выполнения, так как эти символы уже давно ушли в прошлое, но вы можете использовать многосимвольные константы для создания более значимых значений для ваших перечислений.
#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;
}
напечатает
kOne = One.
kTwo = Two.
kThree = Thre
на консоли.
Чтобы сохранить debugString
от создания мусора, каждое перечисление должно быть ровно четыре символа (на OSX в любом случае). Это чрезвычайно зависит от компилятора и платформы. Это хорошо для отладки, но не намного больше.
Конечно, это не сработает, если вам нужно, чтобы перечисления имели определенные значения или значения, которые относятся друг к другу.
Ответ 4
Имена сами по себе не будут доступны, но обычно их достаточно, чтобы дать им явное число:
enum{
dog = 100,
cat = 200,
problem = dog+cat, //trailing comma legal in C and C++11
};