Home > Back-end >  How to reduce number of expansions of second argument in 2-dimensional _Generic?
How to reduce number of expansions of second argument in 2-dimensional _Generic?

Time:11-27

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!*.

CodePudding user response:

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.

  • Related