通用求和宏和嵌套宏展开
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))
我不喜欢这个解决方案,原因有以下三个:
avg$
应该从 sum$
继承可接受的输入类型列表,因为它将只支持 sum$
支持的类型
- 因为输入类型列表是相同的,所以我们最终得到了更多的代码重复。
- 拥有像
_num_map
这样随处可见的宏非常适合您只需要 "a function that take numeric types" 的情况,或者您可以对其他类型执行 _integer_map
和 _floating_map
之类的操作,这使您可以轻松地对类型进行分类。现在您必须复制所有内容,这使得它对全局分类的用处不大。
欺骗 #define _avg_map _num_map
是行不通的,嵌套 _num_map
更深也行不通。
所以我的问题是,有没有办法强制gcc
编译器继续扩展_num_map
inside_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.
我使用 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))
我不喜欢这个解决方案,原因有以下三个:
avg$
应该从sum$
继承可接受的输入类型列表,因为它将只支持sum$
支持的类型- 因为输入类型列表是相同的,所以我们最终得到了更多的代码重复。
- 拥有像
_num_map
这样随处可见的宏非常适合您只需要 "a function that take numeric types" 的情况,或者您可以对其他类型执行_integer_map
和_floating_map
之类的操作,这使您可以轻松地对类型进行分类。现在您必须复制所有内容,这使得它对全局分类的用处不大。
欺骗 #define _avg_map _num_map
是行不通的,嵌套 _num_map
更深也行不通。
所以我的问题是,有没有办法强制gcc
编译器继续扩展_num_map
inside_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.