0

I would like to expand a define inside another define like this:

#define SCALAR_TYPES uint8_t, int8_t, uint16_t, int16_t, double, string
#define VECTOR_TYPES vector<uint8_t>, vector<int8_t>, vector<uint16_t>, vector<int16_t>, vector<double>, vector<string>

#define VARIANT_TYPENAMES SCALAR_TYPES, VECTOR_TYPES

so that VARIANT_TYPENAMES will expand to a list containg the concatenation of the two previously defined macros. Using -E flag shows that the macros are not expanded inside VARIANT_TYPENAMES and I need them defined this way because VECTOR_TYPES will be generated out of SCALAR_TYPES using somethig similar to what is described here.

Is there a way to force the preprocessor to expand macros inside macros?

EDIT

To clarify a bit my intention. I'm trying to implement a general container of data that can be used from the generated C code of matlab algorithms. The interaction with this container has to be done using C only interface since the generated code is C only but the code behind the interface I can use C++ code.

This container can hold different scalar types and vectors of those scalars and this can be done using a std::variant.

On the C interface side I have to provide functions to get and put data from/in the container something like this:

extern "C"{
int getter_for_int(const char *key);
void put_for_intt(const char * key, int val);
void put_for_int_array(const char * key, int *val, uint32_t size);
}

Those are just a few of them but the implementation of all those getters and put-ers are very similar from one data type to another and those functions can be genrated by the preprocessor if I can make the preprocessor expand macros inside #define .

user1934513
  • 693
  • 4
  • 21
  • 3
    Don't use macros. It looks like this is a list of types for `std::variant` or similar, so you can use simple typedef: `using MyVariant = std::variant;` – Yksisarvinen May 04 '20 at 10:45
  • Use `#define EVAL(x) x` ??? – Wör Du Schnaffzig May 04 '20 at 10:50
  • Why do you think that the macros are not expanded? As shown, `VARIANT_TYPENAMES` expands to `uint8_t, ..., vector` Isn't that what you want?. – M Oehm May 04 '20 at 10:51
  • The reason why I want to avoid that is that the list of SCALAR_TYPES may change in time and it is also bigger than the 6 elements that I gave as an example. I also want to generate some functions based on on SCALAR_TYPES and VECTOR_TYPES that have much in common. I can't use templates because this is a C interface. – user1934513 May 04 '20 at 10:52
  • 4
    But you are using templates in `vector<...>` already. – M Oehm May 04 '20 at 10:53
  • 1
    If you want a C API for your C++ code (something wrapped in `extern "C" {` and `}`) then you are restricted to types which are available in C. FYI: [SO: Does extern “C” force limitations on my code?](https://stackoverflow.com/a/9629265/7478597) – Scheff's Cat May 04 '20 at 10:54
  • 2
    Perhaps you should edit your question to show how the macro is supposed to be used. At the moment, it is not clear -- to me, anyway -- what you really want to achieve. – M Oehm May 04 '20 at 11:01
  • Please provide [mcve]. – Marek R May 04 '20 at 11:21

2 Answers2

1

Your question is incomplete since included macros are just working:

#include <iostream>

#define SCALAR_TYPES uint8_t, int8_t, uint16_t, int16_t, double, string
#define VECTOR_TYPES vector<uint8_t>, vector<int8_t>, vector<uint16_t>, vector<int16_t>, vector<double>, vector<string>

#define VARIANT_TYPENAMES SCALAR_TYPES, VECTOR_TYPES

#define STR(X) STRINGIFY(X)
#define STRINGIFY(X) #X

int main()
{
    std::cout << STR((SCALAR_TYPES)) << '\n';
    std::cout << STR((VECTOR_TYPES)) << '\n';
    std::cout << STR((VARIANT_TYPENAMES)) << '\n';
    return 0;
}

produces output:

(uint8_t, int8_t, uint16_t, int16_t, double, string)
(vector<uint8_t>, vector<int8_t>, vector<uint16_t>, vector<int16_t>, vector<double>, vector<string>)
(uint8_t, int8_t, uint16_t, int16_t, double, string, vector<uint8_t>, vector<int8_t>, vector<uint16_t>, vector<int16_t>, vector<double>, vector<string>)

https://wandbox.org/permlink/1JJE838O2CNOOpuT
Here you can find used macros explanation.

Also please avoid use of macros. This should be last resort, since the have lots of disadvantages.

Marek R
  • 32,568
  • 6
  • 55
  • 140
  • You're right, I was mislead by the fact that when using gcc -E I coulnd't see this expansion in the output of the command. Any idea why -E flag doesn't expand all of them? – user1934513 May 04 '20 at 11:36
1

If you don't mind the baroque syntax, you can achieve what you want with an X Macro. Create two separate lists of scalar and vector types:

#define SCALAR_TYPES(_)     \
    _(u32,      uint32_t)   \
    _(double,   double)     \
    _(cstr,     char *)

#define VECTOR_TYPES(_)     \
    _(u32,      uint32_t)   \
    _(double,   double)     \
    _(cstr,     char *)

These two macros are "gerenator macros". They take another macro _ as parameter. That macro must take two arguments, the NAME for creating suitable function names and the TYPE that describes the scalar type or the type of array elements.

To create the interface in the example, first create the needed macros:

#define SCALAR_GET(N, T) T get_##N(const char *key);
#define VECTOR_GET(N, T) T *get_##N##_array(const char *key);
#define SCALAR_PUT(N, T) void put_##N(const char * key, T val);
#define VECTOR_PUT(N, T) void put_##N##_array(const char * key, T *val, uint32_t size);

Then pass them to the two generator macros :

extern "C"{
    SCALAR_TYPES(SCALAR_GET)
    SCALAR_TYPES(SCALAR_PUT)
    VECTOR_TYPES(VECTOR_GET)
    VECTOR_TYPES(VECTOR_PUT)
}

This will produce:

extern "C" {
    uint32_t get_u32(const char *key);
    double get_double(const char *key);
    char *get_cstr(const char *key);
    void put_u32(const char *key, uint32_t val);
    void put_double(const char *key, double val);
    void put_cstr(const char *key, char *val);
    uint32_t *get_u32_array(const char *key);
    double *get_double_array(const char *key);
    char **get_cstr_array(const char *key);
    void put_u32_array(const char *key, uint32_t * val, uint32_t size);
    void put_double_array(const char *key, double *val, uint32_t size);
    void put_cstr_array(const char *key, char **val, uint32_t size);
}

To get your list for the std::variant, use:

#define SCALAR_COMMA(N, T) T,
#define VECTOR_COMMA(N, T) T *,

#define VARIANT_TYPENAMES \
    SCALAR_TYPES(SCALAR_COMMA) VECTOR_TYPES(VECTOR_COMMA)

There's a snag, though: VARIANT_TYPPENAMES will produce a trailing comma. In array initializers, trailing commas are allowed. In enums, you can define a "max" value after the last enumerated value.

But there's also a macro solution for that, shown at the end of the post.


You can also include the "class" of the data – scalar or vector &ndash in the gererator macro.

#define TYPES(_)                            \
    _(SCALAR,   u32,            uint32_t)   \
    _(SCALAR,   double,         double)     \
    _(SCALAR,   cstr,           char *)     \
    _(VECTOR,   u32_array,      uint32_t)   \
    _(VECTOR,   double_array,   double)     \
    _(VECTOR,   cstr_array,     char *)

They define a set of macros that have either the SCALAR_ or ´VECTOR_` as prefix, so that you can create their names with token pasting:

#define SCALAR_TYPE(T) T
#define VECTOR_TYPE(T) T *

#define SCALAR_ARG(T) T val
#define VECTOR_ARG(T) T* val, uint32_t size

Now your maros look like:

#define GET(C, N, T) C##_TYPE(T) get_##N(const char *key);
#define PUT(C, N, T) void put_##N(const char * key, C##_ARG(T));

extern "C"{
    TYPES(GET)
    TYPES(PUT)
}

#define COMMA(C, N, T) C##_TYPE(T),

#define VARIANT_TYPENAMES TYPES(COMMA)

They produce the same result as above.


Finally, about that trailing comma in VARIANT_TYPENAMES: You can get rid of the comma by turning the trailing comma in each macro into a leading comma and then discarding the comma at the head.

#define COMMA(C, N, T) , C##_TYPE(T)

#define TAIL_(A, ...) __VA_ARGS__
#define TAIL(...) TAIL_(__VA_ARGS__)

#define VARIANT_TYPENAMES TAIL(TYPES(COMMA))

This works, because macro arguments can be empty, but it requires two-step expansion to turn TAIL(TYPES(COMMA)) into TAIL_(, T1, T2, T3, ...).


This solution takes some time to get working, especially, because the expanded macros have their whitespace contracted and are not very readable, but once you have your system, you can add new types easily.

The usual caveats to using macros apply. I'd also like to point you to another possible solution: Write a script or program to produce the interfaces for you from definitions that are nicer than X macros and include it in your build process.

M Oehm
  • 28,726
  • 3
  • 31
  • 42