Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to reduce number of expansions of second argument in 2-dimensional _Generic?

Tags:

c

generics

c11

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

like image 849
KamilCuk Avatar asked Nov 24 '21 08:11

KamilCuk


1 Answers

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.

like image 98
tstanisl Avatar answered Oct 24 '22 03:10

tstanisl