You could use a macro to describe the structure, and then the macro could be used to help create the structure definition, and the pretty printer.
So, your code could look like:
PSTRUCT(ABC, (int, a), (char, b), (long, c));
int main () {
ABC abc = {1, 'a', 2};
printf("%s\n", tostring_ABC(&abc));
}
With the advantage that if you define another structure the same way, you will get a new print function available to you.
PSTRUCT(XYZ, (int, x), (double, y), (const char *, z));
The PSTRUCT
macro expands into a structure definition and a function that walks the structure and emits values into a string.
#define PSTRUCT(N, ...) \
typedef struct { XX(PSTRUCT_ARG, __VA_ARGS__) } N; \
const char *tostring_##N (const N *x) { \
static _Thread_local char buf[512]; \
int len = 0; \
XX(PSTRUCT_ARG_PRINT, __VA_ARGS__) \
return buf; \
}
There are helper macros to properly expand the individual fields into the structure definition and to print the field value.
#define PSTRUCT_ARG(X) PSTRUCT_ARG2 X
#define PSTRUCT_ARG2(X, Y) X Y;
#define PSTRUCT_ARG_PRINT(X) PSTRUCT_ARG_PRINT2 X
#define PSTRUCT_ARG_PRINT2(X, Y) \
len += snprintf(buf+len, sizeof(buf)-len, CS(x->Y), #Y, x->Y);
The CS
macro is as defined in Lundin's answer. And the iterating XX
macro is defined as:
#define XX(X, ...) \
XX_X(__VA_ARGS__, XX_5, XX_4, XX_3, XX_2, XX_1) \
(X, __VA_ARGS__)
#define XX_X(_1,_2,_3,_4,_5,X,...) X
#define XX_1(X, _) X(_)
#define XX_2(X, _, ...) X(_) XX_1(X, __VA_ARGS__)
#define XX_3(X, _, ...) X(_) XX_2(X, __VA_ARGS__)
#define XX_4(X, _, ...) X(_) XX_3(X, __VA_ARGS__)
#define XX_5(X, _, ...) X(_) XX_4(X, __VA_ARGS__)
How it works is briefly described in a different answer of mine.
For simplicity, the empty structure case is ignored.
Try it online!