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:


#ifndef CSAFENUM_H
#define CSAFENUM_H

#ifndef __GNUC__
#define __attribute__(x)

#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


Example usage:


#ifndef FRUIT_H
#define FRUIT_H

#include <csafenum.h>


CSE_DECL(Fruit, Apple);
CSE_DECL(Fruit, Banana);
CSE_DECL(Fruit, Pear);
CSE_DECL(Fruit, Strawberry);



#include "fruit.h"


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;


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.

  • 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
    Commented Dec 11, 2016 at 22:36

1 Answer 1


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


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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