Home > front end >  Asking c preprocessor to generate code discriminating on an argument?
Asking c preprocessor to generate code discriminating on an argument?

Time:10-02

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 typedefs to provide single-token identifiers where necessary.

  • Related