Search code examples
c++c-preprocessorx-macros

Is it possible to use X-Macro with std::variant (or with template in general)?


I hope to do the following using X-macro with c++17, but since template parameter does not support trailing comma, it does not work for the std::variant part. Is there someway around it?

#define LIST_OF_TYPES(X) \
  X(Type1)               \
  X(Type2)               \
  X(Type3)

#define MAKE_TYPE(name) class name {};
LIST_OF_TYPES(MAKE_TYPE)
#undef MAKE_TYPE

std::variant<
#define MAKE_VARIANT(name) name,
LIST_OF_TYPES(MAKE_VARIANT)
#undef MAKE_VARIANT
>

Solution

  • Yes, there's a workaround:

    #define EMPTY(...)
    #define IDENTITY(...) __VA_ARGS__
    #define IDENTITY2(...) __VA_ARGS__
    
    std::variant<
    #define MAKE_VARIANT(name) (,) name IDENTITY
    IDENTITY2(EMPTY LIST_OF_TYPES(MAKE_VARIANT) () )
    #undef MAKE_VARIANT
    >
    

    Without IDENTITY2(...) this expands to EMPTY(,) Type1 IDENTITY(,) Type2 IDENTITY(,) Type3 IDENTITY(). IDENTITY2 forces it to expand again, this time to Type1, Type2, Type3.


    Or, with my own macro looping library:

    run on gcc.godbolt.org

    #include <macro_sequence_for.h>
    
    #define LIST_OF_TYPES (Type1)(Type2)(Type3)
    
    #define DECLARE_CLASSES(seq) SF_FOR_EACH(DECLARE_CLASSES_BODY, SF_NULL, SF_NULL,, seq)
    #define DECLARE_CLASSES_BODY(n, d, x) class x {};
    
    #define MAKE_VARIANT(seq) std::variant<SF_FOR_EACH(MAKE_VARIANT_BODY, USE_COMMA, SF_NULL, EMPTY, seq)>
    #define MAKE_VARIANT_BODY(n, d, x) d() x
    #define EMPTY(...)
    #define COMMA(...) ,
    #define USE_COMMA(n, d, x) COMMA
    
    DECLARE_CLASSES(LIST_OF_TYPES) // class Type1 {}; class Type2 {}; class Type3 {};
    MAKE_VARIANT(LIST_OF_TYPES) // std::variant<Type1, Type2, Type3>
    

    Slightly more verbose, but more readable in my taste.

    Here, #define MAKE_VARIANT_BODY(n, d, x) d() x is called for each element, with x being the element, and d initially set to EMPTY (4th argument of SF_FOR_EACH()). After the first (and any subsequent) iteration, d is reassigned to USE_COMMA(...) (aka COMMA), so starting from the second iteration d() expands to , instead of .