2

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?

  1. I'm aware that recursive macro definitions are discouraged since the pre-processor makes a single-pass.
  2. 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.
}
5
  • It looks that you want "map" pattern in c preprocessor. See github.com/swansontec/map-macro/blob/master/map.h Commented Jan 22, 2023 at 9:18
  • That's definitely one way to address the problem. Thanks a lot, @tstanisl! Here's a link to the answer associated with that repo stackoverflow.com/a/13459454/21058589 Commented Jan 22, 2023 at 15:22
  • 1
    There's some confusion about integer types here. You cannot do 1 << 31 because that invokes undefined behavior. Therefore always use 1u << 31. Similarly, you cannot use enumeration constants since they are guaranteed to be of type (signed) int and therefore cannot fit 1u << 31. (A better enum system will get rolled out with C23 where you can set the size of the enum upon declaring it.) Commented Jan 23, 2023 at 13:51
  • That being said, is there a reason that you can't just do HOT_STUFF( HOT(1) | HOT(3) ); etc, with #define HOT(n) (1u << n)? A macro usage like that isn't particularly exotic, a lot of C interfaces expect the caller to do the bit merging with bitwise OR. I don't really understand why you insist on compile-time evaluation since something like if(IS_HOT(expected_entries, SECOND)) is evaluated in run-time anyway. Commented Jan 23, 2023 at 13:54
  • Definitely possible to define the simple HOT(n) macro and OR everything together exactly like you suggested. My question was a bit broader, covering the possibility of a more generic "reduction" operation. The hot operation is an excuse and a single example of this. Perhaps there could be more clever applications. Regarding the types - definitely something to consider. This would be a legal operation, would it not? 1u << ENUM_VALUE The operation would yield an u32 output type, even though the enumerated type (which defines the shift) is underlying i32. Commented Jan 25, 2023 at 4:19

1 Answer 1

1

onehot.h

Implemented with the "Map-Macro" pattern, as suggested.

#ifndef ONEHOT_H
#define ONEHOT_H

// Defining the map-macro operation per
// https://github.com/swansontec/map-macro
#define MAP_OUT

#define EVAL0(...) __VA_ARGS__
#define EVAL1(...) EVAL0 (EVAL0 (__VA_ARGS__))
#define EVAL2(...) EVAL1 (EVAL1 (__VA_ARGS__))
#define EVAL3(...) EVAL2 (EVAL2 (__VA_ARGS__))
#define EVAL4(...) EVAL3 (EVAL3 (__VA_ARGS__))
#define EVAL(...)  EVAL4 (EVAL4 (__VA_ARGS__))

#define MAP_END(...)

#define MAP_GET_END() 0, MAP_END
#define MAP_NEXT0(test, next, ...) next MAP_OUT
#define MAP_NEXT1(test, next) MAP_NEXT0 (test, next, 0)
#define MAP_NEXT(test, next)  MAP_NEXT1 (MAP_GET_END test, next)

#define MAP0(f, x, peek, ...) f(x) MAP_NEXT (peek, MAP1) (f, peek, __VA_ARGS__)
#define MAP1(f, x, peek, ...) f(x) MAP_NEXT (peek, MAP0) (f, peek, __VA_ARGS__)

// Final structure of the map-macro
#define MAP(f, ...) EVAL (MAP1 (f, __VA_ARGS__, (), 0))

// One-Hot Definition
#define ONE_HOT_SHIFT(a) (1u << a) | 
#define ONE_HOT(...) MAP(ONE_HOT_SHIFT, __VA_ARGS__) 0

#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)))
#define INTERSECT_HOT(a, b)     (a & b)
#define UNION_HOT(a, b)         (a | b)
#define EQUALS_HOT(a, b)        (~(a^b))
#define A_CONTAINS_ALL_B(a, b)  (EQUALS_HOT(INTERSECT_HOT(a, b), b))

#endif
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.