john7
john7

Reputation: 123

Passing a generic argument in a C function

I have a struct

typedef struct
{  
    void *l_var;      
    void *r_var;    
}EXPR;

EXPR expr;

I initialize it

expr.l_var = &motor_rt_params[0].position;
expr.r_var = &motor_rt_params[1].position;

Now I want to operate with the values

void SCRIPT_Process(void *l_var, void *r_var, uint32_t oper)
{
    int32_t res;

    switch (oper)
    {
        case OP_PLUS: 
          res = *((??? *) l_var) + *((??? *)r_var);
          break;
        case OP_MINUS: 
          res = *((??? *) l_var) - *((??? *)r_var);
          break; 
    }
}

SCRIPT_Process(expr.l_var , expr.r_var , OP_PLUS);

The variables may be 32, 16, 8 bit. The question - how can I cast it to an appropriate type (instead of (??? *)) in run time?

Upvotes: 0

Views: 204

Answers (4)

0___________
0___________

Reputation: 68013

safe and the same generic. No pointer punning. The type list in the union might be much longer

typedef union
{
    int8_t u8;
    int16_t u16;
    int32_t u32;
}data_t;


int32_t get(data_t *o, int size)
{
    switch (size)
    {
        case 8: 
           return o -> u8;
          break;
        case 16: 
           return o -> u16;
          break; 
        default: 
          return o -> u32;
          break; 
    }
}

void SCRIPT_Process(data_t *l_var, data_t *r_var, uint32_t oper, int sizel, int sizer)
{
    int32_t res;
    int32_t l = get(l_var, sizel);
    int32_t r = get(r_var, sizer);


    switch (oper)
    {
        case OP_PLUS: 
          res = l + r;
          break;
        case OP_MINUS: 
          res = l -r;
          break; 
          /* ..... */
    }
}

Upvotes: 1

KamilCuk
KamilCuk

Reputation: 141900

The variables may be 32, 16, 8 bit.

So you need to know:

  • variables addresses
  • variables types and
  • operation

And you need to pass that information along.

#include <stdio.h>
#include <stdint.h>
#include <assert.h>

enum type_e {
    TYPE_u8,
    TYPE_u16,
    TYPE_u32,
    // TODO: add more, ex. TYPE_INT, TYPE_DOUBLE, etc.
};

enum oper_e {
    OP_PLUS,
    OP_MINUS,
    // TODO: add mode, ex. OP_POW or OP_DIV etc. 
};

void SCRIPT_Process(void *res, const void *l, const void *r, enum type_e type, enum oper_e oper)
{
    switch (oper) {
    case OP_PLUS:
        switch (type) {
        case TYPE_u8:
            *(uint8_t*)res = *(uint8_t*)l + *(uint8_t*)r;
            break;
        case TYPE_u16:
            *(uint16_t*)res = *(uint16_t*)l + *(uint16_t*)r;
            break;
        case TYPE_u32:
            *(uint32_t*)res = *(uint32_t*)l + *(uint32_t*)r;
            break;
        default:
            assert(0);
        }
        break;
     case OP_MINUS:
        // TODO:
        assert(0);
    }
}

int main() {
    uint32_t l = 5, r = 2, res;
    SCRIPT_Process(&res, &r, &l, TYPE_u32, OP_PLUS);
    printf("%d + %d = %d\n", (int)l, (int)r, (int)res);
}

Would be nice to provide a macro to make the code more verbose and with less typing:

#define SCRIPT_PROCESS_MACRO(type, res, l, op, r) \
      *(type*)res = *(type*)l op *(type*)r;

void SCRIPT_Process(void *res, void *l, void *r, enum type_e type, enum oper_e oper)
{
    switch (oper) {
    case OP_PLUS:
        switch (type) {
        case TYPE_u8:
            SCRIPT_PROCESS_MACRO(uint8_t, res, l, +, r);
            break;
        case TYPE_u16:
            SCRIPT_PROCESS_MACRO(uint16_t, res, l, +, r);
            break;
        case TYPE_u32:
            SCRIPT_PROCESS_MACRO(uint32_t, res, l, +, r);
            break;
        default:
            assert(0);
        }
        break;
     case OP_MINUS:
        // TODO:
        assert(0);
    }
}

Or even more simple with more macros, which makes adding new operations and types trivial:

#define SCRIPT_PROCESS_MACRO(type, res, l, op, r) \
      *(type*)res = *(type*)l op *(type*)r;

#define SCRIPT_PROCESS_TYPE_CASES(type, res, l, op, r) \
    switch (type) { \
    case TYPE_u8: SCRIPT_PROCESS_MACRO(uint8_t, res, l, op, r); break; \
    case TYPE_u16: SCRIPT_PROCESS_MACRO(uint16_t, res, l, op, r); break; \
    case TYPE_u32: SCRIPT_PROCESS_MACRO(uint32_t, res, l, op, r); break; \
    default: assert(0); break; \
    }

void SCRIPT_Process(void *res, void *l, void *r, enum type_e type, enum oper_e oper)
{
    switch (oper) {
    case OP_PLUS:
        SCRIPT_PROCESS_TYPE_CASES(type, res, l, +, r);
        break;
     case OP_MINUS:
        SCRIPT_PROCESS_TYPE_CASES(type, res, l, -, r);
        break;
    }
}

Or you could even go with even more generic solution, having separate types for result, left and right operands:

   void SCRIPT_Process(
         void *res, enum type_e restype,
         const void *l, enum type_e ltype,
         const void *r, enum type_e rtype,
         enum oper_e oper) {
     if (restype == TYPE_u8 && ltype == TYPE_u32 && rtype == TYPE_u16 && oper == OP_ADD) {
           *(uint8_t*)res = *(uint32_t*)ltype + *(uint16_t*)rtype;
     } if ( // and so on so on so on so on ....
  }

Upvotes: 0

bruno
bruno

Reputation: 32596

What about to also save the size when you initialize the pointers ?

typedef struct
{  
    void *l_var;      
    void *r_var;    
    size_t sz;
}EXPR;

expr.l_var = &motor_rt_params[0].position;
expr.r_var = &motor_rt_params[1].position;
expr.sz = sizeof(motor_rt_params[1].position);

allowing to to for instance

void SCRIPT_Process(void *l_var, void *r_var, size_t sz, uint32_t oper)
{
    int32_t res;

    switch (oper)
    {
        case OP_PLUS: 
          if (sz == sizeof(int32_t))
            res = *((int32_t *) l_var) + *((int32_t *)r_var);
          else /* suppose a int16_t */
            res = *((int16_t *) l_var) + *((int16_t *)r_var);
          break;
        case OP_MINUS: 
          if (sz == sizeof(int))
            res = *((int32_t *) l_var) - *((int32_t *)r_var);
          else /* suppose a int16_t */
            res = *((int16_t *) l_var) - *((int16_t *)r_var);
    }
}

SCRIPT_Process(expr.l_var , expr.r_var , expr.sz, OP_PLUS);

supposing it is int or short only, I let you adding the case of a int8_t

The advantage also placing the size in the EXPR is to not lost that information / create inconsistencies by error because managed in different piece of code.

Or may be to give the EXPR rather than the fields separately in arguments to SCRIPT_Process ?

May be you also need to know if signed or unsigned, with an additional field, or using an int for the size valuing the positive size for unsigned (4, 2 or 1) and negative size for a signed (-4, -2 -1).

An other way is to save pointers to the right functions rather than the size, a kind of C++ virtual implementation.

Of course all of that supposes you cannot save values as int32_t in the struct and you really need to save the pointers.

Upvotes: 2

John Zwinck
John Zwinck

Reputation: 249592

You can simply store the values in the void*, rather than their address:

expr.l_var = (void*)motor_rt_params[0].position;
expr.r_var = (void*)motor_rt_params[1].position;

Then to use them, cast to intptr_t (values, not pointers). These types are guaranteed to be the same width as a pointer, so you won't need to care anymore about the width of the source values.

Upvotes: 0

Related Questions