Paweł Kurzawa
Paweł Kurzawa

Reputation: 61

_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.

Upvotes: 0

Views: 852

Answers (3)

KamilCuk
KamilCuk

Reputation: 140880

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);
}

Upvotes: 2

Abdulmalek Almkainzi
Abdulmalek Almkainzi

Reputation: 461

One thing you can do is:

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

If you're using a C23 compiler you can use __VA_OPT__ rather than ##__VA_ARGS__

Upvotes: 1

John Bollinger
John Bollinger

Reputation: 180048

There are at least two problems with your attempt:

  1. C does not support 0-length arrays. Some implementations accept them as an extension, as it appears yours does, but as an extension, that may come with caveats, such as not working in the context you're using. But that's moot because

  2. If the uint32_t(*)[0] alternative were selected, then you would end up with a syntactically invalid function call of the form register_read_write(a, p, r,) (note the trailing comma).

Since your macro has non-variadic arguments, you can account for that by absorbing one of them into the variadic arguments:

void register_read_write(action_t action, parameter_t parameter, uint16_t *reg_value);

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

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

#define register_read_write(a, p, ...)  \
        _Generic(&(uint32_t[]){(uintptr_t) __VA_ARGS__},  \
        uint32_t(*)[3]: register_read_write_with_limits, \
        uint32_t(*)[2]: register_read_write_with_upper_limit, \
        uint32_t(*)[1]: register_read_write) \
            ((a), (p), __VA_ARGS__)

void bar() {
    int a = 0;
    int b = 0;
    uint16_t u16 =  0;
    uint32_t min = 0;
    uint32_t max = 0;

    register_read_write(a, b, &u16, min, max);
    register_read_write(a, b, &u16, max);
    register_read_write(a, b, &u16);
}

Now you have a positive-length array type in every generic alternative, and it works fine. Note that the (uintptr_t) cast is required in this because the expected type of the third macro parameter is not compatible with uint32_t, and some compilers will warn about casting a 64-bit pointer to a 32-bit integer. Note well that the cast will affect only the first variadic argument, and that it anyway, neither it nor the implicit conversion of the result to type uint32_t is actually evaluated -- only the type of the expression is required.

Upvotes: 2

Related Questions