我发现自己处于奇怪的境地,试图为某些C代码添加一些语法上的美感。我有三个数字,只是结构。
typedef struct {int x, y, z} coord;
现在我有一些函数需要2个这样的结构作为参数。最简单的一个计算3D空间内的坐标数量定义了两个结构:
static inline int boxes_inside(const coord min, const coord max)
{
return (1 + max.x - min.x) * (1 + max.y - min.y) * (1 + max.z - min.z);
}
我发现自己经常用固定的参数调用它,我发现它很难看
coord foo;
/* initialize foo with something */
int n = boxes_inside((coord){.x = 0, .y = 0, .z = 0}, foo);
没关系这个例子很愚蠢,对于更复杂的功能更有意义。
我以为我会使用_Generic
来传递整数或结构的三元组。
int boxes_inside_cc(const coord min, const coord max);
int boxes_inside_ci(const coord min, const int maxx, const int maxy, const int maxz);
int boxes_inside_ic(const int minx, const int miny, const int minz, const coord max);
int boxes_inside_ii(const int minx, const int miny, const int minz, const int maxx, const int maxy, const int maxz);
#define arg1(a, ...) (a)
#define arg2(a, b ...) (b)
#define arg4(a, b, c, d, ...) (d)
#define boxes_inside(...) _Generic(arg1(__VA_ARGS__), \
coord: _Generic(arg2(__VA_ARGS__), coord: boxes_inside_cc, int: boxes_inside_ci) \
int: _Generic(arg4(__VA_ARGS__), coord: boxes_inside_ic, int: boxes_inside_ii) \
)(__VA_ARGS__)
我认为这样会很好,因为“从未评估过未选择的选择的表达式”。 (ref)但事实证明,由于这是done after preprocessing,因此即使在未选择的选项中,所有宏仍会展开。
特别是,如果我现在进行以下调用:
coord min, max;
/* stuff */
int n = boxes_inside(min, max);
我遇到arg4(__VA_ARGS__)
尝试扩展比实际更多参数的问题,即使_Generic的这个分支以后永远不会被评估。
所以我接着尝试将结构扩展为,总是有足够的参数:
#define boxes_inside_(a, b, c, d, ...) _Generic((a), \
coord: boxes_inside_ii(a, b, c, d.x, d.y, d.z), \
int: boxes_inside_ii(a, b, c, d, __VA_ARGS__) \
)
#define boxes_inside(a, ...) _Generic((a), \
coord: boxes_inside_(a.x, a.y, a.z, __VA_ARGS__) \
int: boxes_inside_(a, __VA_ARGS__) \
)
然而,出于同样的原因,这一点出乎意料地失败了:两个分支都扩展了另一个宏,特别是boxes_inside(min, max)
仍然扩展到我们已知不会使用的分支上的boxes_inside_(min max)
。
那么有办法解决这个问题吗?或者如果你想测试一个超出你可能使用的最小参数数量的参数,那么_Generic
表达式基本上是无用的吗?
答案 0 :(得分:1)
嗯,这就是我们在评论中所讨论的内容,即使它并不令人满意,因为它并不是一个优雅的解决方案。
/* macros that can be reused (possibly with more arguments) */
#define paste2(a, b) a ## b
#define paste(a, b) paste2(a, b)
#define get_seventh(_1, _2, _3, _4, _5, _6, this_one, ...) this_one
#define get_suffix(...) get_seventh(__VA_ARGS__, _6, _5, _4, _3, _2, _1)
/* define all variants with number of arguments suffix */
int boxes_inside_2(const coord min, const coord max);
int boxes_inside_6(const int minx, const int miny, const int minz, const int maxx, const int maxy, const int maxz);
/* make it a _Generic, if several functions have the same number of arguments */
int boxes_inside_ci(const coord min, const int maxx, const int maxy, const int maxz);
int boxes_inside_ic(const int minx, const int miny, const int minz, const coord max);
#define boxes_inside_4(a, ...) _Generic((a),\
coord: boxes_inside_ci) \
int: boxes_inside_ic) \
)(__VA_ARGS__)
/* make macro call itself with the number of arguments pasted after it */
#define boxes_inside(...) paste(boxes_inside, get_suffix(__VA_ARGS__))(__VA_ARGS__)
这种方法的优点是可以获得合理可读的错误消息,例如
warning: implicit declaration of function ‘boxes_inside_3’
表示错误的参数数量,或expected ‘coord {aka const struct <anonymous>}’ but argument is of type ‘int’
如果类型错误。