_超出最小参数数量的通用参数

_Generic parameter beyond minimum number of arguments

我发现自己处于奇怪的境地,试图为某些 C 代码添加一些语法美感。我有三元组数字,它们只是结构。

typedef struct {int x, y, z} coord;

现在我有一些函数将 2 个这样的结构作为参数。最简单的一个统计3D里面坐标的个数space 定义了两个struct:

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__)

我认为这会很好,因为 "the expressions of the selections that are not chosen are are never evaluated." (ref) but it turns out that since this is 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 表达式基本上没用吗?

好吧,这就是我们在评论中讨论的内容,尽管它并不真正令人满意,因为它并不是一个真正优雅的解决方案。

  • 首先,为每个 X 定义 boxes_inside_X 作为可接受的参数数量,必要时使用 _Generic
  • 然后通过在其后粘贴参数数量(或使用 Boost.PP as suggested by )来重载宏。
/* 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’ 如果类型错误。