Created
January 31, 2024 14:21
-
-
Save jdah/1ae0048faa2c627f7f5cb1b68f7a2c02 to your computer and use it in GitHub Desktop.
explaining some C macro magic
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// so a cool trick with macros in C (and C++) is that since macros inside of | |
// macros are stille evaluated by the preprocessor, you can use macro names as | |
// parameters to other macros (and even construct macro names out of out of | |
// parameters!) - so using this trick if we have some macro like | |
// this: | |
#include <stddef.h> | |
#define MY_TYPES_ITER(_F, ...) \ | |
_F(FOO, foo, 0, __VA_ARGS__) \ | |
_F(BAR, bar, 1, __VA_ARGS__) \ | |
_F(BAZ, baz, 2, __VA_ARGS__) \ | |
// then we can first use this trick to create an enum with the members | |
// MY_TYPE_FOO, MY_TYPE_BAR, MY_TYPE_BAZ | |
#define DECL_TYPES_ENUM_MEMBER(uc, lc, i, ...) \ | |
MY_TYPE_##uc = i, | |
typedef enum my_type { | |
MY_TYPES_ITER(DECL_TYPES_ENUM_MEMBER) | |
} my_type_e; | |
// and this expands to | |
// | |
// enum my_types { | |
// MY_TYPE_FOO = 0, | |
// MY_TYPE_BAR = 1, | |
// MY_TYPE_BAZ = 2, | |
// }; | |
// | |
// which can be pretty useful - better yet though, if we have all of our types | |
// defined... | |
typedef struct foo { int x; } foo_t; | |
typedef struct bar { float y; } bar_t; | |
typedef struct baz { const char *z; } baz_t; | |
// then we can *also* use this trick to automatically make a big ol' union of | |
// that type *using* the enum as the type descriminator | |
typedef struct { | |
union { | |
#define DECL_TYPES_UNION_MEMBER(uc, lc, ...) lc##_t lc; | |
MY_TYPES_ITER(DECL_TYPES_UNION_MEMBER) | |
}; | |
my_type_e type; | |
} my_types_t; | |
// and now my_types_t has a union of a foo_t, bar_t, and baz_t | |
// pretty cool, right? | |
// so *even better* than this, if we also list out the fields on each type | |
// (you should probably do this on each struct definition but we'll do it here | |
// for the sake of chronology)... | |
#define FOO_FIELDS(_F, ...) \ | |
_F(x, __VA_ARGS__) | |
#define BAR_FIELDS(_F, ...) \ | |
_F(y, __VA_ARGS__) | |
#define BAZ_FIELDS(_F, ...) \ | |
_F(z, __VA_ARGS__) | |
// and then we use _Generic to map types to another enum... | |
typedef enum field_type { | |
FIELD_TYPE_INT, | |
FIELD_TYPE_FLOAT, | |
FIELD_TYPE_STR, | |
// ... (fill out as you need) | |
} field_type_e; | |
#define TYPE_TO_FIELD_TYPE(x) _Generic(*((x*) NULL), \ | |
int: FIELD_TYPE_INT, \ | |
float: FIELD_TYPE_FLOAT, \ | |
const char*: FIELD_TYPE_STR \ | |
) | |
// and then we define structs to contain information about fields... | |
typedef struct field_desc { | |
field_type_e type; | |
const char *name; | |
int offset, size; | |
} field_desc_t; | |
typedef struct type_desc { | |
field_desc_t fields[16]; | |
} type_desc_t; | |
// then we can use the above macros to autogenerate type info (!) which can be | |
// used for all sorts of stuff, including serialization! | |
#define TYPE_OF_FIELD(parent, field) __typeof__(((parent*) NULL)->field) | |
#define DO_FIELD_DESC(fname, pname) \ | |
{ \ | |
.type = TYPE_TO_FIELD_TYPE(TYPE_OF_FIELD(pname, fname)), \ | |
.name = #fname, \ | |
.size = sizeof(TYPE_OF_FIELD(pname, fname)), \ | |
.offset = offsetof(pname, fname), \ | |
}, | |
#define DO_TYPE_DESC(uc, lc, i, ...) \ | |
[i] = { .fields = { uc##_FIELDS(DO_FIELD_DESC, lc##_t) } }, | |
// this array contains all type info for each field on each of our structs! | |
// automatically !!!! | |
const type_desc_t types[] = { | |
MY_TYPES_ITER(DO_TYPE_DESC) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
tfw no inbuilt serialization