Reputation: 725
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 .
Upvotes: 0
Views: 3054
Reputation: 29136
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 enum
s, 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.
Upvotes: 1
Reputation: 38189
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.
Upvotes: 1