Как эмулировать строго типизированное перечисление в C?
В С++ 03 можно эмулировать строго типизированное перечисление, поместив его в класс (или пространство имен):
struct MyEnum
{
enum enumName
{
VALUE_1 = 1,
VALUE_2,
};
};
и использовать его:
MyEnum::enumName v = MyEnum::VALUE_1;
Можно ли сделать что-то подобное в C? Если да, то как?
Я пробовал вот так, но, конечно, это не работает:
struct A
{
enum aa
{
V1 = 5
};
};
int main()
{
A::aa a1 = A::V1;
enum A::aa a2 = A::V1;
struct A::aa a3 = A::V1;
return 0;
}
Ответы
Ответ 1
Вот мое решение. Имеет несколько преимуществ перед дизайном @Eric:
- Поддерживает тестирование равенства (например,
A_VALUE_0 == value
)
- Не полагается на составные литералы C99
- Можно использовать для присвоения неправильного значения перечислению.
Недостатки:
- Флаги не работают (например,
A_VALUE_0 | A_VALUE_1
)
- Не может быть
switch
'd
- Может запутать среду IDE, где строки ошибок при тестировании на равенство (например,
A_VALUE_0 == B_VALUE_1
)
Примечания:
- НИКОГДА не разыскивайте указатель этого типа. Вызывает сбои быстрее, чем Lamborghini
Здесь реализация (скомпилирована с -Werror
и -pedantic
):
typedef struct A { char empty[1]; } *A; // we use 'empty' so that we don't get a warning that empty structs are a GNU extension
#define A_VALUE_0 ((A) 0x1)
#define A_VALUE_1 ((A) 0x2)
#define A_VALUE_2 ((A) 0x4)
typedef struct B { char empty[1]; } *B;
#define B_VALUE_0 ((B) 0x0)
#define B_VALUE_1 ((B) 0x1)
#define B_VALUE_2 ((B) 0x2)
int main()
{
A a = A_VALUE_0;
int equal = (a == A_VALUE_1); // works!
int euqal = (a == B_VALUE_1) // doesn't work
A flags = A_VALUE_0 | A_VALUE_1; // doesn't work!
switch (a) { // doesn't work
case A_VALUE_0:
puts("value 0");
break;
case A_VALUE_1:
puts("value 1");
break;
case A_VALUE_2:
puts("value 2");
break;
default:
puts("unknown value");
break;
} // doesn't work
// casting works for assignment:
A b = (A) (B_VALUE_2);
return 0;
}
Ответ 2
Вы можете сделать это:
// Declare A to use for an enumeration, and declare some values for it.
typedef struct { int i; } A;
#define A0 ((A) { 0 })
#define A1 ((A) { 1 })
// Declare B to use for an enumeration, and declare some values for it.
typedef struct { int i; } B;
#define B0 ((B) { 0 })
#define B1 ((B) { 1 })
void foo(void)
{
// Initialize A.
A a = A0;
// Assign to A.
a = A1;
// Assign a value from B to A.
a = B0; // Gets an error.
}
Это дает вам некоторую типизацию, но это может быть неприятностью, в зависимости от того, какие другие операции вы хотите выполнить с перечислением и его значениями.
Ответ 3
Поскольку C не предоставляет пространства имен, вы можете использовать префиксы вместо этого.
enum MyEnum {
MyEnumA = 1,
MyEnumB,
MyEnumC
};
enum OtherEnum {
OtherEnumA = 1,
OtherEnumB
};
Затем, для выполнения конкретизации в объявлениях переменных, вы можете объявлять типы для вашего перечисления, например:
typedef enum MyEnum MyEnum;
typedef enum OtherEnum OtherEnum;
Наконец, я не хочу разрешать неявные преобразования OtherEnumB
в тип MyEnum
, Clang предоставляет флаг -Wenum-conversion
(я не думаю, что в GCC есть аналогичный флаг, к сожалению).
/tmp/test.c:24:20: warning: implicit conversion from enumeration type 'enum OtherEnum' to different enumeration type 'MyEnum' (aka 'enum MyEnum') [-Wenum-conversion]
MyEnum value = OtherEnumB;
~~~~~ ^~~~~~~~~~
1 warning generated.
Это имеет то преимущество, что оно простое, легкое для понимания и хорошо работает с вашим (мое, по крайней мере) автозавершением IDE.