-1

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.

  • This is a pretty clever trick for performing generic selection on argument count, by the way. – John Bollinger Aug 05 '21 at 20:47
  • Is there a reason to use `Generic`? You do not seem to care about _types_ of arguments at all, you just cast them to `uint32_t[]` anyway. Just overload the macro on number of arguments. – KamilCuk Aug 05 '21 at 21:34
  • `but it doesn't detect pointer type correctly` Macro is expanded by preprocessor, `_Generic` is parsed by compiler. So `modbus_generic_uint8_read_write(stuf)` __expands always__, because preprocessor expands it. I still do not get fully the point of using `&(uint32_t[])`. – KamilCuk Aug 05 '21 at 23:04
  • @KamilCuk I found an example and adopted it. I don't quite understand the syntax of _Generec yet – Paweł Kurzawa Aug 06 '21 at 05:53

2 Answers2

2

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.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • it worked perfectly. I'm almost starting to understand the idea. If I want to add: void register_uint8_read_write (action_t action, parameter_t parameter, uint8_t * reg_value) Will i need to use nested generic? – Paweł Kurzawa Aug 05 '21 at 20:46
  • Yes, I think you would need a nested generic, @PawełKurzawa. – John Bollinger Aug 05 '21 at 20:50
1

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.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111