There are quite some questions and answers about how to makeenum really type-safe and I didn't find a solution that ensures both type safety and valid values. So I took some ideas and extended it to my own generic solution, somehow resembling toenum classes used in some other languages.
My solution adds the overhead of a pointer dereference for getting the actual enum value, but I think this is a fair deal for situations where you would otherwise have to check for the valid range in a lot of places. Comparing values works naturally with the== operator because there is only one global constant instance of each enum value.
I'm posting it here for review and comments if you like -- please tell me if you spot a bug or any other weakness in my idea. Is there anything I overlooked?
The "implementation" is just a header file:
csafenum.h
#ifndef CSAFENUM_H#define CSAFENUM_H#ifndef __GNUC__#define __attribute__(x)#endif#include <assert.h>/* declare an enum type: CSAFENUM_DECL(TypeName); */#define CSAFENUM_DECL(tname) struct tname##_struct; \ typedef const struct tname##_struct * tname; \ const char *tname##_name(tname e) __attribute__((nonnull(1))); \ int tname##_val(tname e) __attribute__((nonnull(1)))/* declare an enum member: CSE_DECL(TypeName, MemberName); */#define CSE_DECL(tname, name) extern const struct tname##_struct * const name/* define an enum type: CSAFENUM_DEF(TypeName); */#define CSAFENUM_DEF(tname) struct tname##_struct { \ int val; \ const char * const name; \}; \const char *tname##_name(tname tname##_cannot_be_null) \{ \ assert(tname##_cannot_be_null); \ return tname##_cannot_be_null->name; \} \int tname##_val(tname tname##_cannot_be_null) \{ \ assert(tname##_cannot_be_null); \ return tname##_cannot_be_null->val; \} \struct tname##_struct/* define an enum member: CSE_DEF(TypeName, MemberName, IntegerValue); */#define CSE_DEF(tname, name, v) \ static const struct tname##_struct name##_memb = { v, #name }; \ const struct tname##_struct * const name = &name##_memb#endifExample usage:
fruit.h
#ifndef FRUIT_H#define FRUIT_H#include <csafenum.h>CSAFENUM_DECL(Fruit);CSE_DECL(Fruit, Apple);CSE_DECL(Fruit, Banana);CSE_DECL(Fruit, Pear);CSE_DECL(Fruit, Strawberry);#endiffruit.c
#include "fruit.h"CSAFENUM_DEF(Fruit);CSE_DEF(Fruit, Apple, 0);CSE_DEF(Fruit, Banana, 1);CSE_DEF(Fruit, Pear, 2);CSE_DEF(Fruit, Strawberry, 3);main program
#include <stdio.h>#include "fruit.h"int main(){ Fruit a, b, c, d; a = Apple; b = Banana; printf("a = %s (%d)\n", Fruit_name(a), Fruit_val(a)); printf("b = %s (%d)\n", Fruit_name(b), Fruit_val(b)); c = Apple; if (c == a) puts("c == a"); if (c == b) puts("c == b"); d = 0; printf("d = %s (%d)\n", Fruit_name(d), Fruit_val(d)); return 0;}Output
a = Apple (0)
b = Banana (1)
c == a
example: fruit.c:3: Fruit_name: Assertion `Fruit_cannot_be_null' failed.
zsh: abort ./example/example
As pointed out byDarkDust, this does not work inswitch blocks -- pointers aren't allowed to switch on. After thinking about this issue for a while, I get the feeling better not to try solving this, because it would either weaken or overcomplicate (or both) the whole thing.
The whole code is onGitHub.
- 1\$\begingroup\$I found this and your SO answer (stackoverflow.com/a/31165425/20371 ) both via Google and I was about to write something snarky about how this was copied from that, but then saw it's the same guy! Hah.\$\endgroup\$Yawar– Yawar2016-12-11 22:36:13 +00:00CommentedDec 11, 2016 at 22:36
1 Answer1
Another problem with making the enum values pointers, is that it changes the in-memory representation of an enum. There are many situations where it's necessary for the enum to be an int (such as reading/writing structs to/from files).
If you need type-checked enums, it is better to use C++.
You mustlog in to answer this question.
Explore related questions
See similar questions with these tags.