cgenericsnestedc11

C11 nested generics


I am writing a math library and want to have a call like "add" which is generic, takes two parameters v1 and v2, and calls the right function. If v1 is vec2 and v2 is vec2 then it will call vec2_add, if v1 is vec2 and v2 is float it will call vec2_add_float. But if v1 is vec3 and v2 is vec3 it will call vec3_add etc... A small graph is drawn on the bottom

v1 -> vec2, v2-> vec2: call vec2_add v1 -> vec2, v2-> float: call vec2_add_float

v1 -> vec3, v2-> vec3: call vec3_add v1 -> vec3, v2-> float: call vec3_add_float

I did write a small generic code like so:

#define add(v1, v2) _Generic((v1),                  \
                 vec2: _Generic((v2),           \
                         vec2: vec2_add,        \
                         float: vec2_add_float  \
                         ),             \
                 svec3: _Generic((v2),          \
                         vec3: vec3_add,        \
                         float: vec3_add_float  \
                         )              \
                 )(v1, v2)

For some reason, this works with add_float types but not when I try to add vec2 to vec2 or vec3 to vec3, gives me the error message:

‘_Generic’ selector of type ‘vec2’ is not compatible with any association
   30 |                              vec3: _Generic((v2),                      \

What am I doing wrong here?


Solution

  • The problem is caused by a peculiar feature of "generic selection" that all selection expressions must be valid. When v2 is vec2 then expression _Generic(v2, vec3: vec3_add, float: vec3_add_float) is invalid because neither vec3 nor float is compatible with vec2.

    IMO, this is a serious design flaw of generic selection.

    The workaround is using default to handle the common case which would be vec2_add is v1 is vec2, or vec3_add if v1 is vec3.

    #define add(v1, v2) _Generic((v1),              \
                     vec2: _Generic((v2),           \
                             default: vec2_add,     \
                             float: vec2_add_float  \
                             ),                     \
                     vec3: _Generic((v2),           \
                             default: vec3_add,     \
                             float: vec3_add_float  \
                             )                      \
                     )(v1, v2)