I've met my match. I thought I could do this, but like Captain Ahab, I don't know when to call it quits. If all else fails, I'll run a Python script to generate the code, but I'm hoping there's someone out there who is as obsessed with C preprocessor macros as I am...
I'm using GCC. Given a definition like this (or any syntactic changes that would simplify things):
// M(_slot_name, _type, _arg, _accessor)
#define DEFINE_SLOTS(M) \
M(ENABLE_A, bool, ENABLE_A_BITPOS, registry_enable_a) \
M(SLEEP_TIME, uint32_t, sleep_time, registry_sleep_time) \
M(ENABLE_B, bool, ENABLE_B_BITPOS, registry_enable_b) \
M(WAKE_TIME, uint32_t, wake_time, registry_wake_time)
I want the C preprocessor to expand the above into three different things. The first one is easy (generating an enum for each _slot_name
). What I'm getting tripped up on is using the _type
field to conditionally generate different output.
1. An enum for each slot_name:
typedef enum {
ENABLE_A,
SLEEP_TIME,
ENABLE_B,
WAKE_TIME,
} slot_id_t;
(This one is easy - I know how to do it...)
2. An enum that only includes the slots with a bool
type:
typedef enum {
ENABLE_A_BITPOS,
ENABLE_B_BITPOS,
} bitpos_t;
3. Functions whose bodies differ by the _type
field:
bool registry_enable_a(void) { return foo(ENABLE_A_BITPOS); }
uint32_t registry_sleep_time(void) { return bar->sleep_time); }
bool registry_enable_b(void) { return foo(ENABLE_B_BITPOS); }
uint32_t registry_wake_time(void) { return bar->wake_time); }
CodePudding user response:
Provided that the _type
values are all limited to elements of a known-in-advance set and that they contain only single-token identifiers,* you can engage token pasting and some supplementary macros to achieve what you describe. Example:
// M(_slot_name, _type, _arg, _accessor)
#define DEFINE_SLOTS(M) \
M(ENABLE_A, bool, ENABLE_A_BITPOS, registry_enable_a) \
M(SLEEP_TIME, uint32_t, sleep_time, registry_sleep_time) \
M(ENABLE_B, bool, ENABLE_B_BITPOS, registry_enable_b) \
M(WAKE_TIME, uint32_t, wake_time, registry_wake_time)
#define SLOT_ENUM_VALUE(_slot_name, ...) _slot_name,
#define bool_BP_ENUM_VALUE(_arg) _arg,
#define uint32_t_BP_ENUM_VALUE(_arg) /* nothing */
#define BITPOS_ENUM_VALUE(_1, _type, _arg, _4) _type ## _BP_ENUM_VALUE(_arg)
#define bool_SLOT_FUNCTION(_slot_name, _type, _arg, _accessor) \
_type _accessor(void) { return foo(_arg); }
#define uint32_t_SLOT_FUNCTION(_slot_name, _type, _arg, _accessor) \
_type _accessor(void) { return bar->_arg; }
#define SLOT_FUNCTION(_slot_name, _type, _arg, _accessor) \
_type ## _SLOT_FUNCTION(_slot_name, _type, _arg, _accessor)
////////
typedef enum {
DEFINE_SLOTS(SLOT_ENUM_VALUE)
} slot_id_t;
typedef enum {
DEFINE_SLOTS(BITPOS_ENUM_VALUE)
} bitpos_t;
DEFINE_SLOTS(SLOT_FUNCTION)
The key here is that macro expansion does not provide for any conditional logic per se, but it does provide for treating data as code. After all, that's what the X-macro approach you were already using does. Combining that with token pasting can get you a lot more (wholly deterministic) variety in your macro expansions.
*You can use typedef
s to provide single-token identifiers where necessary.