This question is nearly a duplicate, but I figured I'd ask again since it's pretty old and the language may have evolved: Variadic recursive preprocessor macros - is it possible?
I would like to implement a constant ONE_HOT encoding operation and I'm wondering if this is possible using variadic macros.
I would like to compute a constant one-hot encoding of my enum
elements and store it in an uint32
. Basically, reduce all arguments of ONE_HOT(a, b, c, ...)
as (1 << a) | (1 << b) | (1 << c) ...
.
Option A: Bit-shift each entry when computing.
typedef enum {
FIRST,
SECOND,
THIRD,
// More entries
MY_ENUM_NUM_ENTRIES // Guaranteed to never be greater than sizeof(uint32_t)*8 by design.
} MY_ENUM_TYPE;
// In this case, only FIRST and THIRD are "set".
uint32_t expected_entries = (1<<FIRST) | (1<<THIRD);
// Check if set
if(expected_entries & (1<<SECOND)){
// Do something.
}
Option B: Modify the enum
declaration and assign it power-of-2 values.
typedef enum {
FIRST = (1 << 0),
SECOND = (1 << 1),
THIRD = (1 << 2),
// More entries
MY_ENUM_NUM_ENTRIES // Downside: This is now broken. Requires a workaround.
} MY_ENUM_TYPE;
// Upside: The one-hot operation is nicer to read and write.
uint32_t expected_entries = FIRST | THIRD;
// Check if set
if(expected_entries & SECOND){
// Do something.
}
Option C: Variadic Macro?
Is there a way to define my operation such that the one-hot computation is reduced and evaluated at compile-time whenever possible?
- I'm aware that recursive macro definitions are discouraged since the pre-processor makes a single-pass.
- Is it possible to avoid defining 32 macros, one for each arg count? See the linked question at the start.
#define IS_HOT(encoded, bit) (encoded & (1<<bit))
#define SET_HOT(encoded, bit) (encoded | (1<<bit))
#define CLEAR_HOT(encoded, bit) (encoded & (0xFFFFu^(1<<bit)))
// PSEUDO-CODE.
// I want all values (a, b, c, ...) passed into ONE_HOT to be shifted
// and bitwise OR'd together to form the one-hot encoding.
#define ONE_HOT(a, b, c, ...) (1 << a) | (1 << b) | (1 << c) | ONE_HOT(##__VA_ARGS__)
// END PSEUDO-CODE
typedef enum {
FIRST,
SECOND,
THIRD,
// More entries
MY_ENUM_NUM_ENTRIES // Guaranteed to never be greater than sizeof(uint32_t)*8 by design.
// BONUS: we don't need to redefine our original enum.
} MY_ENUM_TYPE;
// Upside: The one-hot operation is nicer to read and write.
uint32_t expected_entries = ONE_HOT(FIRST, THIRD);
// This is terrible
// uint32_t expected_entries = SET_HOT(SET_HOT(encoded, FIRST), THIRD);
// Check if set
if(IS_HOT(expected_entries, SECOND)){
// Do something.
}