19
\$\begingroup\$

There are quite some questions and answers about how to make enum 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 to enum 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

#endif

Example 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);

#endif

fruit.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 by DarkDust, this does not work in switch 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 on GitHub.

\$\endgroup\$
  • 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 Dec 11 '16 at 22:36
2
\$\begingroup\$

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++.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.