Reputation: 11395
I have found myself in strange situation trying to add some syntactic prettyness to some C code. I have triplets of numbers that are just structs.
typedef struct {int x, y, z} coord;
Now I have some functions that take 2 such structs as arguments. The simplest one counts the number of coordinates inside the 3D space defined two structs:
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);
}
I find myself pretty regularly calling it with fixed arguments, which I find to be quite ugly
coord foo;
/* initialize foo with something */
int n = boxes_inside((coord){.x = 0, .y = 0, .z = 0}, foo);
Nevermind this example being silly, it makes more sense for more complicated functions.
I thought I would use _Generic
to pass either triplets of ints, or structs.
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__)
I thought this would be fine since "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, all macros are still expanded even in selections that are not chosen.
In particular, if I now do the following call:
coord min, max;
/* stuff */
int n = boxes_inside(min, max);
I get the problem that arg4(__VA_ARGS__)
tries to expand more parameters than it actually has, even though this branch of the _Generic will never be evaluated later on.
So I then tried to expand the structs to, always have enough arguments:
#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__) \
)
However this unsurprisingly fails with the same reason: both branches expand the other macro, in particular boxes_inside(min, max)
still expands to boxes_inside_(min max)
on the branch we already know won't be used.
So is there a way around this? Or are _Generic
expressions basically useless if you want to test a parameter that's beyond the minimal number of parameters you might use?
Upvotes: 3
Views: 547
Reputation: 11395
Well, here goes what we discussed in the comments, even though it is not really satisfying, since it's not really an elegant solution.
boxes_inside_X
for every X
being an acceptable number of arguments, using _Generic
when necessary./* 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__)
The upside of this approach is that you get reasonably readable error messages, for example
warning: implicit declaration of function ‘boxes_inside_3’
for the wrong number of arguments, orexpected ‘coord {aka const struct <anonymous>}’ but argument is of type ‘int’
if the types are wrong.Upvotes: 1