3

Have a library that accepts a struct defining the row format at compile time. Different users have different variations of the struct.

One example:

typedef struct { INT16U a, INT32S b } logrow_t;

At run time I would like to iterate through this struct and print each element and its type.

The first solution that comes to mind is to build a table:

typedef struct { char var_name[16], char var_type[16], int bytelen } logrow_desc_t;

logrow_desc_t descriptions[] = { { "a", "INT16U", 2 }, { "b", "INT32S", 4 } };

Is there a better solution that allows for any user of the library to specify a different row struct? Is there a way to leverage the preprocessor/compiler to build the descriptions table at compile time?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
mfarver
  • 141
  • 4

3 Answers3

1

Is there a way to leverage the preprocessor/compiler to build the descriptions table at compile time?

If you and your users don't mind specifying the row format in a macro (let's say LOGROW) rather than directly in the definition of logrow_t, a variation of the method described in this answer from luser droog to "Foreach macro on macros arguments" can be used.
I put the generic, reusable part in the file ppnarg.h:

/*
 * The PP_NARG macro evaluates to the number of arguments that have been
 * passed to it.
 * Laurent Deniau, "__VA_NARG__," 17 January 2006, <comp.std.c> (29 November 2007).
 */
#define PP_NARG(...)    PP_NARG_(__VA_ARGS__,PP_RSEQ_N())
#define PP_NARG_(...)   PP_ARG_N(__VA_ARGS__)
#define PP_ARG_N( \
        _1, _2, _3, _4, _5, _6, _7, _8, _9,_10,  \
        _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
        _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
        _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
        _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
        _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
        _61,_62,_63,N,...) N
#define PP_RSEQ_N() \
        63,62,61,60,                   \
        59,58,57,56,55,54,53,52,51,50, \
        49,48,47,46,45,44,43,42,41,40, \
        39,38,37,36,35,34,33,32,31,30, \
        29,28,27,26,25,24,23,22,21,20, \
        19,18,17,16,15,14,13,12,11,10, \
        9,8,7,6,5,4,3,2,1,0

/* need extra level to force extra eval */
#define Paste(a,b) a ## b
#define XPASTE(a,b) Paste(a,b)

/* APPLYXn variadic X-Macro by M Joshua Ryan      */
/* Free for all uses. Don't be a jerk.            */
/* I got bored after typing 15 of these.          */
/* You could keep going upto 64 (PPNARG's limit). */
#define APPLYX1(a)           X(a)
#define APPLYX2(a,b)         X(a) X(b)
#define APPLYX3(a,b,c)       X(a) X(b) X(c)
#define APPLYX4(a,b,c,d)     X(a) X(b) X(c) X(d)
#define APPLYX5(a,b,c,d,e)   X(a) X(b) X(c) X(d) X(e)
#define APPLYX6(a,b,c,d,e,f) X(a) X(b) X(c) X(d) X(e) X(f)
#define APPLYX7(a,b,c,d,e,f,g) \
    X(a) X(b) X(c) X(d) X(e) X(f) X(g)
#define APPLYX8(a,b,c,d,e,f,g,h) \
    X(a) X(b) X(c) X(d) X(e) X(f) X(g) X(h)
#define APPLYX9(a,b,c,d,e,f,g,h,i) \
    X(a) X(b) X(c) X(d) X(e) X(f) X(g) X(h) X(i)
#define APPLYX10(a,b,c,d,e,f,g,h,i,j) \
    X(a) X(b) X(c) X(d) X(e) X(f) X(g) X(h) X(i) X(j)
#define APPLYX11(a,b,c,d,e,f,g,h,i,j,k) \
    X(a) X(b) X(c) X(d) X(e) X(f) X(g) X(h) X(i) X(j) X(k)
#define APPLYX12(a,b,c,d,e,f,g,h,i,j,k,l) \
    X(a) X(b) X(c) X(d) X(e) X(f) X(g) X(h) X(i) X(j) X(k) X(l)
#define APPLYX13(a,b,c,d,e,f,g,h,i,j,k,l,m) \
    X(a) X(b) X(c) X(d) X(e) X(f) X(g) X(h) X(i) X(j) X(k) X(l) X(m)
#define APPLYX14(a,b,c,d,e,f,g,h,i,j,k,l,m,n) \
    X(a) X(b) X(c) X(d) X(e) X(f) X(g) X(h) X(i) X(j) X(k) X(l) X(m) X(n)
#define APPLYX15(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o) \
    X(a) X(b) X(c) X(d) X(e) X(f) X(g) X(h) X(i) X(j) X(k) X(l) X(m) X(n) X(o)
#define APPLYX_(M, ...) M(__VA_ARGS__)
#define APPLYXn(...) APPLYX_(XPASTE(APPLYX, PP_NARG(__VA_ARGS__)), __VA_ARGS__)

//#define ZIP2(a,b) /* to be defined by application */
#define ZIP4(a,b,c,d)                   ZIP2(a,b)                   ZIP2(c,d)
#define ZIP6(a,b,c,d,e,f)               ZIP4(a,b,c,d)               ZIP2(e,f)
#define ZIP8(a,b,c,d,e,f,g,h)           ZIP6(a,b,c,d,e,f)           ZIP2(g,h)
#define ZIP10(a,b,c,d,e,f,g,h,i,j)      ZIP8(a,b,c,d,e,f,g,h)       ZIP2(i,j)
#define ZIP12(a,b,c,d,e,f,g,h,i,j,k,l)  ZIP10(a,b,c,d,e,f,g,h,i,j)  ZIP2(k,l)
// define more of those ZIPxx() if more arguments are needed
#define ZIP(...) APPLYX_(XPASTE(ZIP, PP_NARG(__VA_ARGS__)), __VA_ARGS__)

In your application, this could be used so:

// only this line is to be defined by the users:
#define LOGROW  INT16U, a, INT32S, b

// the following defines `logrow_t` and `descriptions` according to the above:
#include <stddef.h>
#include "ppnarg.h"

#define ZIP2(a, b)  a b;
typedef struct { ZIP(LOGROW) } logrow_t;
#undef  ZIP2
typedef struct { char *name, *type; size_t size; } logrow_desc_t;
#define ZIP2(a, b)  { #b, #a, sizeof (a) },
logrow_desc_t descriptions[] = { ZIP(LOGROW) };
#undef  ZIP2
Armali
  • 18,255
  • 14
  • 57
  • 171
0

Seems some work with macros. For example the following code:

#include <stdio.h>
#include <stddef.h>
#include <stdint.h>

// first we need a foreach macro for each 2 arguments apply a function
#define FOR2EACH_2(f, _1,_2)       f(_1, _2)
#define FOR2EACH_4(f, _1,_2, ...)  f(_1, _2) FOR2EACH_2(f, __VA_ARGS__)
#define FOR2EACH_6(f, _1,_2, ...)  f(_1, _2) FOR2EACH_4(f, __VA_ARGS__)
#define FOR2EACH_N(_8,_7,_6,_5,_4,_3,_2,_1,N,...) FOR2EACH_##N
#define FOR2EACH(f, ...)  FOR2EACH_N(__VA_ARGS__,8,7,6,5,4,3,2,1)(f, __VA_ARGS__)

// structure for holding description
struct struct_desc_s {
    const char *name;
    const char *type;
    size_t size;
};

// then a callback for struct fields
#define STRUCT_WITH_DESC_FIELD(type, name)  type name;
// a callback for description values
#define STRUCT_WITH_DESC_DESC(type, name)  { #name, #type, sizeof(type) },
// and finally our definition
#define STRUCT_WITH_DESC(NAME, ...) \
struct NAME { \
    FOR2EACH(STRUCT_WITH_DESC_FIELD, __VA_ARGS__) \
}; \
const struct struct_desc_s NAME##_desc[] = { \
    FOR2EACH(STRUCT_WITH_DESC_DESC, __VA_ARGS__) \
};

STRUCT_WITH_DESC(logrow,
    uint16_t, a,
    uint32_t, b
)

int main() {
    for (size_t i = 0; i < sizeof(logrow_desc)/sizeof(logrow_desc[0]); ++i) {
        printf("%s %s %zu\n", logrow_desc[i].name, logrow_desc[i].type, logrow_desc[i].size);
    }
}

outputs:

a uint16_t 2
b uint32_t 4
KamilCuk
  • 120,984
  • 8
  • 59
  • 111
0

Consider XMacro.

#define LOWGROW_XMACRO \
  X(a, INT16U) \
  X(b, INT32S)

Now you can define a struct as:

typedef struct {
#define X(NAME,TYPE) TYPE NAME;
LOWGROW_XMACRO
#undef X
} lowgrow_t;

Or create descriptions:

logrow_desc_t descriptions[] = {
#define X(NAME,TYPE) { #NAME, #TYPE, sizeof(TYPE) },
LOWGROW_XMACRO
#undef X
};

Possible uses are limitless.

One could put the above macros to a dedicated header (i.e. "lowgrow.hpp") and include it after defining the XMacro:

#define LOWGROW_XMACRO \
...
#include "lowgrow.hpp"

Alternatively, the LOWGROW_XMACRO could take another macro as an argument. This higher-order macros to make more functional API.

// lowgrow.hpp
#define LOWGROW_MAKE_STRUCT_MEMBER(NAME, TYPE) TYPE NAME;
#define LOWGROW_MAKE_DESCRIPTION(NAME, TYPE)   { #NAME, #TYPE, sizeof (TYPE) },

#define LOWGROW_DO_STUFF(X)      \
typedef struct {                         \
X(LOWGROW_MAKE_STRUCT_MEMBER)    \
} lowgrow_t;                     \
                                 \
logrow_desc_t descriptions[] = { \
X(LOWGROW_MAKE_DESCRIPTION)      \
};

Now the macro could be instantiated with:

#include "lowgrow.hpp"

#define LOWGROW_XMACRO(X) \
  X(a, INT16U) \
  X(b, INT32S)

LOWGROW_DO_STUFF(LOWGROW_XMACRO)

expands to (after adding a few newlines for clarity):

typedef struct { INT16U a; INT32S b; } lowgrow_t;
logrow_desc_t descriptions[] = {
{ "a", "INT16U", sizeof (INT16U) },
{ "b", "INT32S", sizeof (INT32S) },
};
tstanisl
  • 13,520
  • 2
  • 25
  • 40