cvariadic-macros

_Generic function with several parameters


I am using several similar functions and want to make one overloadable.
The basic function takes 3 parameters, and its successive expansions take 4 or more.

#define register_read_write(action, parameter, reg_value, ...) 
        _Generic(&(uint32_t[]){__VA_ARGS__}, 
        uint32_t(*)[2]: register_read_write_with_limits,
        uint32_t(*)[1]: register_read_write_with_upper_limit)
            ((action), (parameter), (reg_value), (__VA_ARGS__))

declarations

void register_read_write_with_limits(access_t action, parameter_t parameter, uint16_t *reg_value, 
                                     uint32_t value_min, uint32_t value_max);

void register_read_write_with_upper_limit(access_t action, parameter_t parameter, uint16_t *reg_value, 
                                     uint32_t value_max);

and it works fine but i can't add a basic function with 3 parameters:

void register_read_write(access_t action, parameter_t parameter, uint16_t *reg_value)

i try:

#define register_read_write(action, parameter, reg_value, ...) 
        _Generic(&(uint32_t[]){__VA_ARGS__}, 
        uint32_t(*)[2]: register_read_write_with_limits,
        uint32_t(*)[1]: register_read_write_with_upper_limit,
        uint32_t(*)[0]: register_read_write)
            ((action), (parameter), (reg_value), (__VA_ARGS__))

but:

error: '_Generic' selector of type 'uint32_t ()[0]' {aka 'long unsigned int ()[0]'} is not compatible with any association #define register_read_write(action, parameter, reg_value, ...) _Generic(&(uint32_t[]){VA_ARGS},

following the blow....

I'm developing my generic functions and ran into another problem.

#define FIRST_ARG(value, ...) (value)

#define generic_uint8_read_write(action, parameter, ...) 
        _Generic(&(uint32_t[]){(uintptr_t)__VA_ARGS__},
        uint32_t(*)[1]: register_uint8_read_write)(action, parameter, __VA_ARGS__)

#define generic_uint16_read_write(action, parameter, ...) _Generic(&(uint32_t[]){(uintptr_t)__VA_ARGS__},                
         uint32_t(*)[2]: register_read_write_with_limits,
        uint32_t(*)[1]: register_read_write_with_upper_limit,
        uint32_t(*)[0]: register_read_write)(action, parameter, __VA_ARGS__)

#define generic_read_write(action, parameter, ...)   
        _Generic(FIRST_ARG(__VA_ARGS__),
        uint8_t*: generic_uint8_read_write(action, parameter, __VA_ARGS__),
        uint16_t* : generic_uint16_read_write(action, parameter, __VA_ARGS__))

I don't know why, but it doesn't detect pointer type correctly.

i change

#define generic_read_write(action, parameter, value, ...)   
        _Generic((value),
        uint8_t*: generic_uint8_read_write(action, parameter, value, __VA_ARGS__),
        uint16_t* : generic_uint16_read_write(action, parameter, value, __VA_ARGS__))

and still failed :-(

Please give me some suggestions.

regards
P.S. I am looking for a good tutorial on how the "_Generic" functionality works.


Solution

  • Consider such implementation:

    #include <stdint.h>
    #include <stdio.h>
    #define BODY  { printf("%s\n", __func__); }
    void func_u8_0(int action, int param, uint8_t *reg) BODY
    void func_u8_1(int action, int param, uint8_t *reg, int min) BODY
    void func_u8_2(int action, int param, uint8_t *reg, int min, int max) BODY
    void func_u16_0(int action, int param, uint16_t *reg) BODY
    void func_u16_1(int action, int param, uint16_t *reg, int min) BODY
    void func_u16_2(int action, int param, uint16_t *reg, int min, int max) BODY
    
    #define register_read_write_1(a, p, r) \
        _Generic((r) \
        , uint8_t*: func_u8_0 \
        , uint16_t*: func_u16_0 \
        )
    #define register_read_write_2(a, p, r, min) \
        _Generic((r) \
        , uint8_t*: func_u8_1 \
        , uint16_t*: func_u16_1 \
        )
    #define register_read_write_3(a, p, r, min, max) \
        _Generic((r) \
        , uint8_t*: func_u8_2 \
        , uint16_t*: func_u16_2 \
        )
    #define register_read_write_N(_3,_2,_1,N,...) register_read_write_##N
    #define register_read_write(a, p, ...) \
        register_read_write_N(__VA_ARGS__,3,2,1)(a, p, __VA_ARGS__)(a, p, __VA_ARGS__)
    
    int main() {
        register_read_write(1, 1, (uint8_t*)0);
        register_read_write(1, 1, (uint8_t*)0, 1);
        register_read_write(1, 1, (uint8_t*)0, 1, 1);
        register_read_write(1, 1, (uint16_t*)0);
        register_read_write(1, 1, (uint16_t*)0, 1);
        register_read_write(1, 1, (uint16_t*)0, 1, 1);
    }
    

    I.e. detect number of arguments with macros, and detect types with _Generic. One tool for one job.


    Revisiting the answer, I think it would be nice to have it in one place instead of multiple. The following counts the arguments and overloads on the pointer of an array with a size depending on the size of register and argument count. In this code instead of offsetting of sizeof(*r) you could also just do _Generic(r, uint8_t*: 10, uint16_t*: 20).

    #include <stdint.h>
    #include <stdio.h>
    
    #define BODY  { printf("%s\n", __func__); }
    void func_u8_0(int action, int param, uint8_t *reg) BODY
    void func_u8_1(int action, int param, uint8_t *reg, int min) BODY
    void func_u8_2(int action, int param, uint8_t *reg, int min, int max) BODY
    void func_u16_0(int action, int param, uint16_t *reg) BODY
    void func_u16_1(int action, int param, uint16_t *reg, int min) BODY
    void func_u16_2(int action, int param, uint16_t *reg, int min, int max) BODY
    
    #define ARGCOUNT_N(_0,_1,_2,N,...)  N
    #define ARGCOUNT(...)  ARGCOUNT_N(__VA_OPT__(,) __VA_ARGS__,2,1,0)
    
    #define register_read_write(a, p, r, ...) \
        _Generic((char(*)[sizeof(*r) * 10 + ARGCOUNT(__VA_ARGS__)])0 \
        , char(*)[sizeof( uint8_t) * 10 + 0]: func_u8_0 \
        , char(*)[sizeof( uint8_t) * 10 + 1]: func_u8_1 \
        , char(*)[sizeof( uint8_t) * 10 + 2]: func_u8_2 \
        , char(*)[sizeof(uint16_t) * 10 + 0]: func_u16_0 \
        , char(*)[sizeof(uint16_t) * 10 + 1]: func_u16_1 \
        , char(*)[sizeof(uint16_t) * 10 + 2]: func_u16_2 \
        )(a, p, r __VA_OPT__(,) __VA_ARGS__)
    
    int main() {
        register_read_write(1, 1, (uint8_t*)0);
        register_read_write(1, 1, (uint8_t*)0, 1);
        register_read_write(1, 1, (uint8_t*)0, 1, 1);
        register_read_write(1, 1, (uint16_t*)0);
        register_read_write(1, 1, (uint16_t*)0, 1);
        register_read_write(1, 1, (uint16_t*)0, 1, 1);
    }