-1

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 from sum$ as it will only support the types that sum$ 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?

A_User
  • 397
  • 1
  • 9

1 Answers1

0

No, C macros are never recursive. 6.10.3.4 p 2 reads:

If the name of the macro being replaced is found during this scan of the replacement list (not including the rest of the source file’s preprocessing tokens), it is not replaced. Furthermore, if any nested replacements encounter the name of the macro being replaced, it is not replaced. These nonreplaced macro name preprocessing tokens are no longer available for further replacement even if they are later (re)examined in contexts in which that macro name preprocessing token would otherwise have been replaced.

Jens Gustedt
  • 76,821
  • 6
  • 102
  • 177