0

I'm trying to generate a bunch of types off a simple template using macros to remove some of the copy and paste work this would usually require but I've ran into a bit of a problem. These are my macros to generate the types:

#define VEC2_TYPES_LIST(X) \
    X(Vec2f, float, ) \
    X(Vec2d, double, ) \
    X(Vec2u, uint32_t, ) \
    X(Vec2i, int32_t, )

#define X_TYPE(u, t, s) \ 
typedef union u \
{ \
    struct \
    { \
        t x; \
        t y; \
    }; \
 \
    struct \
    { \
        t elem[2]; \
    }; \
 \
    s \
} u;       

VEC2_TYPES_LIST(X_TYPE)

The problem is that I want to create common functions to these types that will be dynamically dispatched at runtime through a vtable. So as well as making the function definitions I also wanted to put them into a structure as function pointers. Something like this:

#define VEC2_FUNCTIONS_LIST( ?? )
    X(u, u##_add, (u a, u b)) // return type, function name, params

To generate this code:

Vec2f vec2f_add(Vec2f a, Vec2f b);
...
Vec2i vec2i_add(Vec2i a, Vec2i b);

struct Vec2fVTable
{
    Vec2f (*vec2f_add)(Vec2f a, Vec2f b);
};

// same for other types

I have no idea how to go about doing this so I'd appreciate if someone could teach me.

  • [Article about why you should avoid using macros](https://www.linkedin.com/pulse/why-considered-bad-practice-use-macros-c-herbert-elwood-gilliland-iii#:~:text=Macros%20should%20never%20take%20the,that%20only%20they%20can%20do.%22&text=Unless%20of%20course%20you%20want,then%20this%20macro%20helps%20you!&text=An%20alternative%20to%20macro%20is%20inline%20function%20for%20some%20use%20cases.%22) (although it is a good intention to remove boilerplate) – gkhaos Mar 24 '21 at 18:54
  • Does this answer your question? [Macro concatenation, function name generation](https://stackoverflow.com/questions/15522115/macro-concatenation-function-name-generation) – gkhaos Mar 24 '21 at 18:59
  • I'm sure together with [this clever idea](https://stackoverflow.com/a/3400770/9233560) you can find a way... – gkhaos Mar 24 '21 at 19:03
  • @gkhaos My issue is less with the concatenating but more the fact that I can't concieve of a way to do a list within a list. My types are generated from a list and from that type list I want to generate the functions from another list, using the types generated from the first one. – Alexandre Deus Mar 24 '21 at 21:10
  • @gkhaos: (1) this question is tagged C, but the article you linked is about macro use in C++. (2) the article is actually debunking "macros are evil", so not sure why you link to it :) [X macros](https://en.wikipedia.org/wiki/X_Macro) are a valid use of macros till this day in both C and C++. – Yakov Galka Mar 25 '21 at 02:39
  • The preprocessor (at least for the use-case) of C and C++ will behave in the same way (check this [link](https://stackoverflow.com/questions/5085533/is-a-c-preprocessor-identical-to-a-c-preprocessor) for differences). Although I didn't think this though, I'm certain that this is possible using string concatenation, please have a look at this [example](https://godbolt.org/z/18891odb7). The biggest problem I see is that your function names are lower case, while the class name is capitalized – gkhaos Mar 25 '21 at 08:26
  • By the time you start implementing vtables in C, it's time to re-examine if you are using right tool for the job. In my experience it gets very messy very fast. I recommend that you switch to a programming language that supports these features natively. – user694733 Mar 25 '21 at 09:39
  • 1
    @user694733 You are right that it would be simple to implement this in a language like C++. However, I'm writing a DLL library with some exports to use in other languages so I would still have to write a thin C layer even if I were to use C++, hence the decision to just do it in C from the start. – Alexandre Deus Mar 25 '21 at 12:43
  • Ok. But why are you creating you creating different vtable for each different type? The whole point behind polymorphism is that base class defines the vtable type and sub classes instance it. If each type defines its own vtable type then you don't get benefit of polymorphism, but only pay the price of indirect function calls. In the other words: Why are you using dynamic dispatch? – user694733 Mar 25 '21 at 13:40
  • @user694733 I intend to give each method that uses the types a different implementation for various SIMD intrinsics, their support being detected at runtime and their vtables properly set to the best implementation avaliable. – Alexandre Deus Mar 25 '21 at 14:39
  • @YakovGalka (2) I linked the article to encourage people reading this to look for alternatives. Why rely on the pre-processor, when you can rely on modern frameworks? – gkhaos Apr 13 '21 at 17:49
  • @gkhaos: while the preprocessors of C and C++ are similar, the arguments for using it or not are very different. E.g. you cannot tell a C programmer to "just replace it with templates", because they don't have any. The fact remains that there are still legit reasons to use the preprocessor in either language, and even more so in C. Since the OP is writing in C for one reason or another, suggesting they switch to a different language is outright irrelevant to their question. – Yakov Galka Apr 13 '21 at 20:03
  • @YakovGalka IMHO the reason "I develop C, so it's ok" doesn't work. I'm not saying that you should switch languages, I'm suggesting to use other languages for your advantage. Imagine building your program without using a build tool like CMake... So why not do the same with your code? Instead of building objects, build source files... – gkhaos Apr 14 '21 at 07:57
  • @gkhaos: Some people [see things](http://harmful.cat-v.org/software/) in a [different way](http://suckless.org/sucks/). – Yakov Galka Apr 14 '21 at 15:41

1 Answers1

1

I have found a solution:

// Generate export functions and function pointers for the vtables
#define X_FUNC(ret, name, param) ACC_EXPORT ret name param;
#define X_FUNC_PTR(ret, name, param) ret (*name) param;

// Generate types
#ifdef X86_SIMD
    #define VEC_TYPE_LIST(X) \
        VEC_DIMENSIONS(X, f, float, __m128 simd;) \
        VEC_DIMENSIONS(X, d, double, __m128d simd;) \
        VEC_DIMENSIONS(X, i, int32_t, __m128i simd;) \
        VEC_DIMENSIONS(X, u, uint32_t, __m128i simd;)
#else
    #define VEC_TYPE_LIST(X) \
        VEC_DIMENSIONS(X, f, float, ) \
        VEC_DIMENSIONS(X, d, double, ) \
        VEC_DIMENSIONS(X, i, int32_t, ) \
        VEC_DIMENSIONS(X, u, uint32_t, )
#endif

// Generate dimensions
#define VEC_DIMENSIONS(X, suf, type, simd) \
    X(suf, 2, type, simd) \
    X(suf, 3, type, simd) \
    X(suf, 4, type, simd)

// Generate element structs
#define VEC_ELEMENTS_STRUCT_2(type) struct { type x; type y; };
#define VEC_ELEMENTS_STRUCT_3(type) struct { type x; type y; type z; };
#define VEC_ELEMENTS_STRUCT_4(type) struct { type x; type y; type z; type w; };

// Generate functions
#define VEC_FUNCTIONS_LIST(X, funcPref, type, vecType) \
X(vecType, funcPref##_add, (vecType a, vecType b)) \
X(vecType, funcPref##_subtract, (vecType a, vecType b)) \

// Type template
#define X_VEC_TYPE(suf, dim, type, simd) \
typedef union Vec##dim##suf \
{ \
    VEC_ELEMENTS_STRUCT_##dim(type) \
    struct { type elements[dim]; }; \
    simd \
} Vec##dim##suf; \
VEC_FUNCTIONS_LIST(X_FUNC, vec##dim##suf, type, Vec##dim##suf) \
typedef struct Vec##dim##suf##VTable \
{ \
    VEC_FUNCTIONS_LIST(X_FUNC_PTR, vec##dim##suf, type, Vec##dim##suf) \
} Vec##dim##suf##VTable; \

// Generate everything
VEC_TYPE_LIST(X_VEC_TYPE)