I wrote a generic sum macro using the C11 _Generic keyword, but I'm having problems expanding the macro inside other generic function definitions.
Here is the sum macro with all the helper macros:
// Helper macros
#define _num_map($, _) $(char)_ $(short)_ $(int)_ $(long)_ $(float)_ $(double)
#define _comma ,
#define _gencase(_T, _F) _T: _F(_T)
#define _alen(_xs) sizeof(_xs)/sizeof(_xs[0])
#define _generic(_map, _M, _gen, ...) _Generic((_gen), _map(_M, _comma))(__VA_ARGS__)
// Define sum functions
#define _sum_func(_T) __sum_generic_##_T
#define _def_sum(_T) \
_T _sum_func(_T)(_T *xs, size_t len) \
{ \
_T n = (_T) 0; \
for (size_t i = 0; i < len; i++) \
n += xs[i]; \
return n; \
}
_num_map(_def_sum,)
#undef _def_sum
// Define sum$ and suma$ generic macros:
#define _gen_sum(_T) _gencase(_T, _sum_func)
#define sum$(_xs, _len) _generic(_num_map, _gen_sum, (_xs)[0], _xs, _len)
#define suma$(_xs) sum$(_xs, _alen(_xs))
_num_map
is used in order to avoid having to duplicate the types in both the function definition macro and the _Generic expression. It is essentially just a list, with built-in macro-mapping, think of the _
character as ,
while the $
character is the mapped macro.
The $
symbol at the end of sum$
and suma$
is just a personal style-guide that means "generic macro, be aware of possible weird compiler errors/warnings if you use it wrong."
This works pretty well and can be used like this:
int main(void)
{
int nums[] = {1, 2, 3, 4};
double dubs[] = {1.1, 2.2, 3.3, 4.4};
double dubs0[] = {};
printf("Sum: %d\n", suma$(nums));
printf("DSum: %lf\n", suma$(dubs));
printf("Zero DSum: %lf\n", suma$(dubs0));
return 0;
}
The problem comes when I attempt to do something like this:
// Define avg functions
#define _avg_func(_T) __avg_generic_##_T
#define _def_avg(_T) \
double _avg_func(_T)(_T *xs, size_t len) \
{ \
return ((double) sum$(xs, len)) / len; \
}
_num_map(_def_avg,)
#undef _def_avg
// Define avg$ and avga$ generic macros:
#define _gen_avg(_T) _gencase(_T, _avg_func)
#define avg$(_xs, _len) _generic(_num_map, _gen_avg, (_xs)[0], _xs, _len)
#define avga$(_xs) avg$(_xs, _alen(_xs))
_num_map(_def_avg,)
fails to expand properly, looking at the output of gcc -E sum-generic.c
I can see that _num_map
is treated as a function call when sum$
is expanded inside _def_avg
. So for some reason gcc
doesn't want to keep expanding _num_map
when it is already expanding _num_map
.
Resolving the problem involves duplicating _num_map
:
// Define avg functions
#define _avg_map($, _) $(char)_ $(short)_ $(int)_ $(long)_ $(float)_ $(double)
#define _avg_func(_T) __avg_generic_##_T
#define _def_avg(_T) \
double _avg_func(_T)(_T *xs, size_t len) \
{ \
return ((double) sum$(xs, len)) / len; \
}
_avg_map(_def_avg,)
#undef _def_avg
// Define avg$ and avga$ generic macros:
#define _gen_avg(_T) _gencase(_T, _avg_func)
#define avg$(_xs, _len) _generic(_avg_map, _gen_avg, (_xs)[0], _xs, _len)
#define avga$(_xs) avg$(_xs, _alen(_xs))
I don't like this solution for three reasons:
avg$
should inherit the list of acceptable input types fromsum$
as it will only support the types thatsum$
supports- Because the list of input types are identical, we end up with more code duplication.
- Having macros like
_num_map
that are used everywhere is nice for when you just need "a function that take numeric types" or you could do something like_integer_map
and_floating_map
for the other types, which lets you easily categorize the types. Now you have to duplicate everything which makes it less useful for categorization globally.
Cheating with #define _avg_map _num_map
does not work, neither does nesting _num_map
deeper.
So my question is, is there a way to force the gcc
compiler to continue expanding _num_map
inside _num_map(_def_avg)
? Or do I just have to duplicate _num_map
?