通用求和宏和嵌套宏展开

Generic sum macro and nested macro expansion

我使用 C11 _Generic 关键字编写了通用求和宏,但在其他通用函数定义中扩展宏时遇到问题。

这是包含所有辅助宏的求和宏:

// 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 用于避免在函数定义宏和 _Generic 表达式中重复类型。它本质上只是一个列表,具有内置的宏映射,将 _ 字符视为 ,$ 字符是映射的宏。

sum$suma$ 末尾的 $ 符号只是个人风格指南,意思是 "generic macro, be aware of possible weird compiler errors/warnings if you use it wrong."

这很好用,可以这样使用:

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;
}

当我尝试做这样的事情时,问题就来了:

// 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,) 无法正确展开,查看 gcc -E sum-generic.c 的输出,我可以看到当 sum$ 在内部展开时 _num_map 被视为函数调用 _def_avg。所以出于某种原因 gcc 不想继续扩展 _num_map 当它已经扩展 _num_map.

解决问题需要复制 _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))

我不喜欢这个解决方案,原因有以下三个:

欺骗 #define _avg_map _num_map 是行不通的,嵌套 _num_map 更深也行不通。

所以我的问题是,有没有办法强制gcc编译器继续扩展_num_mapinside_num_map(_def_avg)?或者我只需要复制 _num_map?

不,C 宏从不递归。 6.10.3.4 p 2 读取:

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.