I have the following code:
int add_ii(int a, int b) { return a + b; }
unsigned add_iu(int a, unsigned b) { return a + b; }
unsigned add_ui(unsigned a, int b) { return a + b; }
unsigned add_uu(unsigned a, unsigned b) { return a + b; }
#define add(LEFT, RIGHT) \
_Generic(LEFT \
,int: _Generic(RIGHT \
,int: add_ii \
,unsigned: add_iu \
) \
,unsigned: _Generic(RIGHT \
,int: add_ui \
,unsigned: add_uu \
) \
)(LEFT, RIGHT)
int main() {
return add(1, add(2, add(3, 4)));
}
The problem is that RIGHT
is expanded for each _Generic
case. In the above, RIGHT
is used 3 times, so add(2, add(3, 4))
is fully expanded 3 times, so add(3, 4)
is expanded 9 times. The number grows exponentially with each nested usage and with each additional type handled inside _Generic
. This is not an issue in itself - but it causes preprocessor to generate very big output, which significantly stalls the compilation process and causes very high compilation memory usage.
Is there any way to write 2-dimensional _Generic
so that RIGHT
is not expanded each time for each type?
Switching LEFT
with RIGHT
is of course not an option and wouldn't do much - LEFT
will be then expanded that many times.
I know I can use GNU extensions, but the idea is that I would like to do without them:
#define add(LEFT, RIGHT0) ({
__auto_type LEFT = LEFT0;
__auto_type RIGHT = RIGHT0;
_Generic(LEFT, .....)(LEFT, RIGHT)
})
I can write kind of a map, but that erases return type information and requires all function types to be cast to a common type, or to use some unsafe va_arg
:
unsigned add_ii_adaptor(unsigned a, unsigned b) { return add_ii(a, b); }
unsigned add_iu_adaptor(unsigned a, unsigned b) { return add_iu(a, b); }
unsigned add_ui_adaptor(unsigned a, unsigned b) { return add_ui(a, b); }
static unsigned (*const funcarr[])(unsigned a, unsigned b) = {
add_ii_adaptor,
add_iu_adaptor,
add_ui_adaptor,
add_uu,
};
#define typeidx(x) _Generic((x), int: 0, unsigned 1)
#define add(LEFT, RIGHT) \
funcarr[typeidx(LEFT) << 1 | typeidx(RIGHT)](LEFT, RIGHT)
Is there a method that I'm overlooking?
Background: However, what is the ultimate point here?
I'm implementing Integer safety n2792 without GNU extensions here. There are 26 types I want to support, ckd_add(x, y, z)
makes it 17576 combinations of types. (This can be reduced, but nonetheless). In the examples above the return type is the promoted type of operands, so unsigned + int = unsigned
, but like for example it could be long + char = long *or unsigned long!*
.
It is possible use _Generic
with mapping a tuple to an integer. You need a type parameterized by an integer and C provides such a family ... arrays.
However, arrays cannot be used as dispatched argument because an array decays to pointer. However, pointers to arrays do not decay.
Just compute a size of array using typeidx
-like macro and use is as a size of an new array type. Add 1
because C forbids zero-size arrays.
Next form a pointer to it using compound literal.
E.q. (int(*)[3]) { 0 }
.
Finally, use the type of this literal to dispatch a proper function pointer.
#define TYPE_TO_NUM(X) _Generic((X), int: 0, unsigned: 1)
#define add(LEFT, RIGHT) \
_Generic( \
(int(*)[1 + 2 * TYPE_TO_NUM(LEFT) + TYPE_TO_NUM(RIGHT)]) { 0 } \
,int(*)[1]: add_ii \
,int(*)[2]: add_iu \
,int(*)[3]: add_ui \
,int(*)[4]: add_uu \
)(LEFT, RIGHT)
The solution still has exponential complexity due to expanding LEFT
and RIGHT
twice but is far faster than the original one.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With